一次在 classpath 使用通配符导致的偶发问题排查与建议

news2024/12/23 18:50:10

说起 Classpath,使用 Java 的同学应该都不会陌生。不过,目前的项目基本都会使用 Maven 等构建工具管理,开发过程中也会使用高度智能化的 IDE,在日常使用中直接涉及 Classpath 操作可能不多。前段时间遇到一个跟 Classpath 相关的偶发问题,本文记录这个问题的排查过程与建议。

文章目录

    • 结论与建议
      • 问题原因
      • 建议
    • 问题现象
    • 排查过程
      • 本地运行集成测试,无法复现
    • 调整测试框架 MySQL Connector/J 版本为 8.0.22
    • 调整 Proxy 集成测试镜像依赖
      • 移除原版 MySQL 驱动
      • 将 Proxy 集成测试镜像调整为 MySQL Connector/J 8.0.22
        • 测试报错,问题复现
    • 调整 Proxy 集成测试镜像 classpath 顺序
      • 将 aws-mysql-jdbc 顺序置于 mysql-connector-java 后
        • 测试通过,问题未复现
      • 将 mysql-connector-java 顺序置于 aws-mysql-jdbc 后
        • 测试报错,问题复现
      • 移除 aws-mysql-jdbc
        • 测试通过,下结论
    • 关于 classpath
      • classpath 案例与文档解读
      • classpath 通配符模拟实验

结论与建议

问题原因

  • 两个包含同名类的 JAR 包在同一个目录下(类同名但行为不一致);
  • Proxy 启动脚本 classpath 使用了通配符,不同环境下 lib 下面的 jar 加载顺序存在差异;
  • 在不同环境、时刻启动 ShardingSphere-Proxy,实际使用的 JAR 存在不确定性。

建议

开发者在为项目引入依赖的时候,需要对依赖有一个基本的了解, 避免同时引入两个不同坐标但同源的依赖(例如本文的 MySQL Connector/J 和 AWS JDBC Driver for MySQL)。

问题现象

前段时间,在我个人仓库运行 ShardingSphere-Proxy 集成测试的时候发现,有个 MySQL Proxy 集成测试用例失败了,而且重试了 2 次仍然失败。
但是,第二天点了一下重试后,测试居然过了,下图的 Latest attempt #4 虽然显示是失败,但这个之前失败的 case 已经通过了。
在这里插入图片描述

回想一下,也跟踪了一下代码提交记录,最近并没有修改 MySQL 协议或 MySQL 相关的逻辑。

报错信息很明显,测试用例原本期望是一个 boolean true,实际上却拿到了一个 1。

Error:  Failures: 
Error:  org.apache.shardingsphere.test.e2e.engine.dql.GeneralDQLE2EIT.assertExecuteQuery[proxy: shadow -> MySQL -> Literal -> SELECT order_id, user_id, order_name, type_char, type_boolean, type_smallint, type_enum, type_decimal, type_date, type_time, type_timestamp FROM t_shadow WHERE user_id = ?]
Error:    Run 1: GeneralDQLE2EIT.assertExecuteQuery:58->assertExecuteQueryForStatement:71->BaseDQLE2EIT.assertResultSet:78->BaseDQLE2EIT.assertRows:93->BaseDQLE2EIT.assertRow:111 
Expected: is "true"
     but: was "1"
Error:    Run 2: GeneralDQLE2EIT.assertExecuteQuery:58->assertExecuteQueryForStatement:71->BaseDQLE2EIT.assertResultSet:78->BaseDQLE2EIT.assertRows:93->BaseDQLE2EIT.assertRow:111 
Expected: is "true"
     but: was "1"
[INFO] 
Error:  org.apache.shardingsphere.test.e2e.engine.dql.GeneralDQLE2EIT.assertExecuteQuery[proxy: shadow -> MySQL -> Placeholder -> SELECT order_id, user_id, order_name, type_char, type_boolean, type_smallint, type_enum, type_decimal, type_date, type_time, type_timestamp FROM t_shadow WHERE user_id = ?]
Error:    Run 1: GeneralDQLE2EIT.assertExecuteQuery:60->assertExecuteQueryForPreparedStatement:86->BaseDQLE2EIT.assertResultSet:78->BaseDQLE2EIT.assertRows:93->BaseDQLE2EIT.assertRow:111 
Expected: is "true"
     but: was "1"
Error:    Run 2: GeneralDQLE2EIT.assertExecuteQuery:60->assertExecuteQueryForPreparedStatement:86->BaseDQLE2EIT.assertResultSet:78->BaseDQLE2EIT.assertRows:93->BaseDQLE2EIT.assertRow:111 
Expected: is "true"
     but: was "1"
[INFO] 
Error:  org.apache.shardingsphere.test.e2e.engine.dql.GeneralDQLE2EIT.assertExecute[proxy: shadow -> MySQL -> Literal -> SELECT order_id, user_id, order_name, type_char, type_boolean, type_smallint, type_enum, type_decimal, type_date, type_time, type_timestamp FROM t_shadow WHERE user_id = ?]
Error:    Run 1: GeneralDQLE2EIT.assertExecute:97->assertExecuteForStatement:112->BaseDQLE2EIT.assertResultSet:78->BaseDQLE2EIT.assertRows:93->BaseDQLE2EIT.assertRow:111 
Expected: is "true"
     but: was "1"
Error:    Run 2: GeneralDQLE2EIT.assertExecute:97->assertExecuteForStatement:112->BaseDQLE2EIT.assertResultSet:78->BaseDQLE2EIT.assertRows:93->BaseDQLE2EIT.assertRow:111 
Expected: is "true"
     but: was "1"
[INFO] 
Error:  org.apache.shardingsphere.test.e2e.engine.dql.GeneralDQLE2EIT.assertExecute[proxy: shadow -> MySQL -> Placeholder -> SELECT order_id, user_id, order_name, type_char, type_boolean, type_smallint, type_enum, type_decimal, type_date, type_time, type_timestamp FROM t_shadow WHERE user_id = ?]
Error:    Run 1: GeneralDQLE2EIT.assertExecute:99->assertExecuteForPreparedStatement:129->BaseDQLE2EIT.assertResultSet:78->BaseDQLE2EIT.assertRows:93->BaseDQLE2EIT.assertRow:111 
Expected: is "true"
     but: was "1"
Error:    Run 2: GeneralDQLE2EIT.assertExecute:99->assertExecuteForPreparedStatement:129->BaseDQLE2EIT.assertResultSet:78->BaseDQLE2EIT.assertRows:93->BaseDQLE2EIT.assertRow:111 
Expected: is "true"
     but: was "1"

排查过程

本地运行集成测试,无法复现

问题在 GitHub Actions 上连续失败了 3 次,那本地是否有可能快速复现?

将 GitHub Actions 上的 Proxy 测试镜像导入到本地,使用相同的命令运行测试:

./mvnw -nsu -B install -f test/e2e/suite/pom.xml -Dspotless.apply.skip=true -Dit.cluster.env.type=DOCKER -Dit.cluster.adapters=proxy -Dit.run.modes=Cluster -Dit.cluster.databases=MySQL -Dit.scenarios=shadow

本地运行的时候有个小插曲。

集成测试使用的 MySQL server 镜像为:mysql/mysql-server:5.7

在运行测试的时候发现,测试启动的 MySQL server 版本存在一定差异。GitHub Actions 实际运行的 MySQL server 版本为:5.7.40-1.2.10-server

[INFO ] 2023-01-12 02:21:42.869 [docker-java-stream--198238272] 🐳 [mysql/mysql-server:5.7] - Pull complete. 8 layers, pulled in 13s (downloaded 150 MB at 11 MB/s)
[INFO ] 2023-01-12 02:21:42.876 [main] 🐳 [mysql/mysql-server:5.7] - Creating container for image: mysql/mysql-server:5.7
[INFO ] 2023-01-12 02:21:43.134 [main] 🐳 [mysql/mysql-server:5.7] - Container mysql/mysql-server:5.7 is starting: 78e6a4132439b0383add9d9fc8306690a94a31d421cdeb559801c81cec223353
[INFO ] 2023-01-12 02:21:43.501 [docker-java-stream-1124194819] shadow:mysql - STDOUT: [Entrypoint] MySQL Docker Image 5.7.40-1.2.10-server

但是本地运行集成测试拉下来的 MySQL server 实际是:5.7.36-1.2.6-server

[INFO ] 2023-01-13 13:24:41.494 [docker-java-stream-1264158127] 🐳 [mysql/mysql-server:5.7] - Pull complete. 8 layers, pulled in 31s (downloaded 127 MB at 4 MB/s)
[INFO ] 2023-01-13 13:24:41.501 [main] 🐳 [mysql/mysql-server:5.7] - Creating container for image: mysql/mysql-server:5.7
[INFO ] 2023-01-13 13:24:41.738 [main] 🐳 [mysql/mysql-server:5.7] - Container mysql/mysql-server:5.7 is starting: b1909b805a9db021633b928242f605307533b09b6a1dee5487b481b770405776
[INFO ] 2023-01-13 13:24:42.111 [docker-java-stream--726063482] shadow:mysql - STDOUT: [Entrypoint] MySQL Docker Image 5.7.36-1.2.6-server

本地删除镜像后通过命令重新 pull,结果还是一样的,后来发现是本地使用了阿某云的镜像加速服务。删除 Registry 后,重新 pull 镜像的结果与 GitHub Actions 一致。

在本地运行了多次集成测试,全部通过,未能复现问题。

调整测试框架 MySQL Connector/J 版本为 8.0.22

考虑 MySQL 5.7 和 8.0 对 boolean 的支持存在差异,调整测试框架 MySQL Connector/J 版本为 8.0.22。
在这里插入图片描述
调整后,集成测试报错,但是报错的是日期时间相关的断言,与本问题无关,不继续展开调查。
DQL 用例均通过,因此可以排除是 MySQL 客户端版本问题。

调整 Proxy 集成测试镜像依赖

查看镜像文件的时候发现:集成测试镜像依赖中还有 aws-mysql-jdbc 依赖。之前检查过 ShardingSphere 的 MySQL 协议等相关代码没有发生变动,但是没有想起代码中增加过 AWS 的 MySQL JDBC 驱动。

移除原版 MySQL 驱动

有没有可能是 Proxy 实际用了 aws-mysql-jdbc 驱动连接的 MySQL?

FROM 9ea895f0bb57
RUN rm /opt/shardingsphere-proxy/lib/mysql-connector-java-5.1.47.jar 

移除后,Proxy 由于无法加载 MySQL 驱动 XA 相关类,无法正常启动。

查看 aws-mysql-jdbc 源码发现,AWS 驱动是基于 MySQL Connector/J 8.0.x 开发的,也持续在同步上游 MySQL Connector/J 8.x 的代码 ,因此 aws-mysql-jdbc 驱动相当于是个 MySQL 8.x 的驱动。

相关源码:https://github.com/awslabs/aws-mysql-jdbc/blob/main/src/main/user-impl/java/com/mysql/cj/jdbc/Driver.java

将 Proxy 集成测试镜像调整为 MySQL Connector/J 8.0.22

FROM 9ea895f0bb57
RUN rm /opt/shardingsphere-proxy/lib/mysql-connector-java-5.1.47.jar /opt/shardingsphere-proxy/lib/aws-mysql-jdbc-1.1.2.jar
COPY mysql-connector-java-8.0.22.jar /opt/shardingsphere-proxy/lib/mysql-connector-java-8.0.22.jar

测试报错,问题复现

[ERROR] org.apache.shardingsphere.test.e2e.engine.dql.GeneralDQLE2EIT.assertExecute[proxy: shadow -> MySQL -> Literal -> SELECT order_id, user_id, order_name, type_char, type_boolean, type_smallint, type_enum, type_decimal, type_date, type_time, type_timestamp FROM t_shadow WHERE user_id = ?]
[ERROR]   Run 1: GeneralDQLE2EIT.assertExecute:97->assertExecuteForStatement:112->BaseDQLE2EIT.assertResultSet:78->BaseDQLE2EIT.assertRows:93->BaseDQLE2EIT.assertRow:111 
Expected: is "true"
     but: was "1"
[ERROR]   Run 2: GeneralDQLE2EIT.assertExecute:97->assertExecuteForStatement:112->BaseDQLE2EIT.assertResultSet:78->BaseDQLE2EIT.assertRows:93->BaseDQLE2EIT.assertRow:111 
Expected: is "true"
     but: was "1"
[INFO] 
[ERROR] org.apache.shardingsphere.test.e2e.engine.dql.GeneralDQLE2EIT.assertExecute[proxy: shadow -> MySQL -> Placeholder -> SELECT order_id, user_id, order_name, type_char, type_boolean, type_smallint, type_enum, type_decimal, type_date, type_time, type_timestamp FROM t_shadow WHERE user_id = ?]
[ERROR]   Run 1: GeneralDQLE2EIT.assertExecute:99->assertExecuteForPreparedStatement:129->BaseDQLE2EIT.assertResultSet:78->BaseDQLE2EIT.assertRows:93->BaseDQLE2EIT.assertRow:111 
Expected: is "true"
     but: was "1"
[ERROR]   Run 2: GeneralDQLE2EIT.assertExecute:99->assertExecuteForPreparedStatement:129->BaseDQLE2EIT.assertResultSet:78->BaseDQLE2EIT.assertRows:93->BaseDQLE2EIT.assertRow:111 
Expected: is "true"
     but: was "1"
[INFO] 
[ERROR] Tests run: 60, Failures: 4, Errors: 0, Skipped: 0
[INFO] 
[INFO] --- maven-failsafe-plugin:2.22.0:verify (integration-tests) @ shardingsphere-test-e2e-suite ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  50.246 s

说明该集成测试可能与 aws-mysql-jdbc 相关。

但如何证明?
能否不增减依赖的前提下复现问题?

调整 Proxy 集成测试镜像 classpath 顺序

Proxy 启动脚本 classpath 定义如下:

CLASS_PATH=.:${DEPLOY_DIR}/lib/*:${EXT_LIB}/*

由于 lib 目录使用的是通配符,可能在不同环境下,lib 内的 jar 会有不同的加载顺序。

将 aws-mysql-jdbc 顺序置于 mysql-connector-java 后

把 aws-mysql-jdbc JAR 移动到顺序靠后的 ext-lib

FROM 9ea895f0bb57
RUN mkdir /opt/shardingsphere-proxy/ext-lib && mv /opt/shardingsphere-proxy/lib/aws-mysql-jdbc-1.1.2.jar /opt/shardingsphere-proxy/ext-lib/

测试通过,问题未复现

[INFO] Tests run: 64, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 40.525 s - in JUnit Vintage
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  50.701 s

将 mysql-connector-java 顺序置于 aws-mysql-jdbc 后

把 mysql-connector-java JAR 移动到顺序靠后的 ext-lib

FROM 9ea895f0bb57
RUN mkdir /opt/shardingsphere-proxy/ext-lib && mv /opt/shardingsphere-proxy/lib/mysql-connector-java-5.1.47.jar /opt/shardingsphere-proxy/ext-lib/

测试报错,问题复现

移除 aws-mysql-jdbc

FROM 9ea895f0bb57
RUN mkdir /opt/shardingsphere-proxy/ext-lib && mv /opt/shardingsphere-proxy/lib/mysql-connector-java-5.1.47.jar /opt/shardingsphere-proxy/ext-lib/ && rm /opt/shardingsphere-proxy/lib/aws-mysql-jdbc-1.1.2.jar

测试通过,下结论

所以该问题结论:因为依赖冲突导致的,加上 classpath 通配符导致的 JAR 顺序无法保证,导致 ShardingSphere-Proxy 在不同时刻使用了不同的 JAR 引发的问题。
通过调试 Proxy 进程可以进一步证实该问题,此处不再赘述。

关于 classpath

classpath 案例与文档解读

对于依赖相关问题,我想起之前遇到过一个案例:
classpath 的两个目录下有两个不同版本的 MySQL Connector/J,大致情形如下:

lib/mysql-connector-java-8.0.22.jar
ext-lib/mysql-connector-java-8.0.27.jar

而 classpath 的写法为:

java -cp lib/*:ext-lib/*

最终,ShardingSphere-Proxy 实际使用的都是 lib 目录下 8.0.22 版本的驱动。

关于 classpath 可以参考文档:

https://docs.oracle.com/javase/7/docs/technotes/tools/windows/classpath.html

其中有一段:

The order in which the JAR files in a directory are enumerated in the expanded class path is not specified and may vary from platform to platform and even from moment to moment on the same machine. A well-constructed application should not depend upon any particular order. If a specific order is required then the JAR files can be enumerated explicitly in the class path.
在这里插入图片描述

意思大致就是:一个通配符目录下的 JAR 加载顺序无法得到保证。

因此,如果一个目录下有两个 JAR 并且 JAR 包含了相同的类,在不同环境或同一环境的不同时刻启动程序,实际使用的 JAR 是无法保证的。

classpath 通配符模拟实验

创建一个 Dependency.java ,将 println 的内容改为 version A 打一个 JAR dependency-a.jar,再将 println 的内容改为 version B 打一个 JAR dependency-b.jar

λ ~/test_cp/ tree
.
├── Main.java
└── lib
    ├── Dependency.java
    ├── dependency-a.jar
    └── dependency-b.jar

1 directory, 4 files

λ ~/test_cp/ cat Main.java lib/Dependency.java 

public class Main {

    public static void main(String[] args) {
        new Dependency().getVersion();
    }
}

public class Dependency {

    public void getVersion() {
        System.out.println("I'm version B");
    }
}

使用通配符指定 classpath 目录,启动程序。

λ ~/test_cp/ java -cp 'lib/dependency-a.jar' Main.java
I'm version A

λ ~/test_cp/ java -cp 'lib/dependency-b.jar' Main.java
I'm version B

λ ~/test_cp/ java -cp 'lib/*' Main.java

使用通配符结果如何?
运行结果为 B。
在这里插入图片描述

多运行几次命令,用 Docker 运行,结果都是一样的。
在这里插入图片描述

等第二天再跑一下看结果会不会变。

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

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

相关文章

【My Electronic Notes系列——触发器】

目录 序言: 🏆🏆人生在世,成功并非易事,他需要破茧而出的决心,他需要永不放弃的信念,他需要水滴石穿的坚持,他需要自强不息的勇气,他需要无畏无惧的凛然。要想成功&…

【栈】单调栈详情介绍及其运用

单调栈单调栈的概述(Overview)何时使用单调栈模拟单调递增栈单调栈的运用(算法练习题)模板【练习一、单调栈】739. 每日温度【练习二、单调栈哈希表】496. 下一个更大元素 I【练习三、单调栈循环数组】503. 下一个更大元素 II【练…

Word处理控件Aspose.Words功能演示:使用 C++ 处理 Word 文档中的目录

Aspose API支持流行文件格式处理,并允许将各类文档导出或转换为固定布局文件格式和最常用的图像/多媒体格式。 Aspose.words是一种高级Word文档处理API,用于执行各种文档管理和操作任务。API支持生成,修改,转换,呈现和…

4EVERLAND IPFS CID部署,一键部署Uniswap

近日,4EVERLAND推出IPFS CID部署,开发者可以复制IPFS CID,一键部署到4EVERLAND。 一键部署,无需通过Github Repo,只需要知道CID即可。一键跨平台部署项目到 Arweave 或 ICP。了解IPFS CID,通过4EVERLAND绑…

漫谈广告机制设计 | 开篇语

很久没有写文章了,oCPC实践录的专栏还没有写完,我就换工作了,去了M公司,做的内容与oCPC不怎么相关,对于其中的问题思考也没有那么多了,好在专栏的核心思想已经基本阐明了。在M公司也已经快两年了&#xff0…

青龙+WxPusher实现资产推送

1.首先注册WXpusher: https://wxpusher.zjiecode.com/admin/login 扫码注册创建应用 确定完就会出现一个token,一定先复制保存起来,因为只显示一次,没存后期就只能重置了。 关闭后,这个页面有二维码和链接&#xff0…

CSS定位详解

文章目录定位为什么要使用定位定位的组成定位模式静态定位:按照标准流特性摆放,没有边偏移相对定位:元素在移动位置的时候,是相对于它原来的位置来说的绝对定位:在移动位置的时候相对与祖先元素固定定位:元…

C语言:指针详解

往期文章 C语言:初识C语言C语言:分支语句和循环语句C语言:函数C语言:数组C语言:操作符详解 目录往期文章前言1. 指针是什么2. 指针和指针类型3. 野指针4. 指针运算4.1 指针-整数4.2 指针-指针4.3 指针的关系运算5. 二…

“小灵通”的风雨往事

最近,有一部叫做《狂飙》的国产电视剧火遍全网,相信大家都看到了。在剧中,出现了一个通信名词,不知道在座各位有没有关注到。没错,这个名词,就是“小灵通”。《狂飙》剧的主角高启强,原本是个卖…

Web3.0 · 基础层技术 · SCQA模型趣谈密码学

【小木箱成长营】密码学系列教程: Web3.0 基础层技术 密码学在移动端应用与实践 一、序言 Hello,我是小木箱,欢迎来到小木箱成长营密码学系列教程,今天将分享 Web3.0 基础层技术 SCQA 模型趣谈密码学。 SCQA 模型趣谈密码学主…

第一章 opencv与python介绍及环境搭建

目录1.python安装2.opencv3.pycharm安装4.conda环境搭建(my)1.python安装 网上教程很多就不写了,推荐使用python3.8.2及以上版本 2.opencv opencv简单介绍:opencv是一个开源的计算机视觉库,可以在windows、MacOS、Linux等操作系统上运行。 …

Day878.count(*)问题 -MySQL实战

count(*)问题 Hi,我是阿昌,今天学习记录的是关于count(*)问题。 在开发系统的时候,可能经常需要计算一个表的行数,比如一个交易系统的所有变更记录总数。 这时候可能会想,一条 select count(*) from t 语句不就解决…

【自动化测试】从0开始玩转docker—— 02软件配置

目的 CI / CD在目前各类互联网企业中已然成为推动软件开发行为的重要基础设施服务。同样的对于测试团队来说更是有着举足轻重的重大意义,无论是测试左移的具象化提现亦或是持续测试的顺利开展,掌握这一技能已是广大软件测试工程师的必修课。分享这一技术…

第一章:3D点云应用领域分析

🌞欢迎来到点云的世界 🌈博客主页:卿云阁 💌欢迎关注🎉点赞👍收藏⭐️留言📝 🌟本文由卿云阁原创! ✉️希望可以和大家一起完成进阶之路! 🙏作者…

力扣(LeetCode)401. 二进制手表(2023.02.03)

二进制手表顶部有 4 个 LED 代表 小时(0-11),底部的 6 个 LED 代表 分钟(0-59)。每个 LED 代表一个 0 或 1,最低位在右侧。 例如,下面的二进制手表读取 “3:25” 。 (图源&#xff…

C语言基础知识(56)

下面的C程序的输出是什么#include<stdio.h>intmain(){int a 0;while(a <printf("HI")){a;}return0;}该代码将打印 3 次HI。 printf()函数将返回它正在打印的字符数&#xff0c;并将其与a进行比较。 由于 printf() 的返回值为 2&#xff0c;HI 将被打印 2 次…

字符函数和字符串函数

&#x1f331;博客主页&#xff1a;大寄一场. &#x1f331;系列专栏&#xff1a;C语言学习笔记 &#x1f618;博客制作不易欢迎各位&#x1f44d;点赞⭐收藏➕关注 目录 前言 一、字符串函数 1. 1 strlen 2. 长度不受限制的字符串函数&#xff08;操作的是整个字符串&#…

Linux-用户权限相关命令

1.用户和权限的基本概念1.1基本概念用户是Linux系统工作中重要的一环&#xff0c;用户管理包括用户与组管理在Linux系统中&#xff0c;不论是由本机或是远程登录系统&#xff0c;每个系统都必须拥有一个账号&#xff0c;并且对于不同的系统资源拥有不同的使用权限在Linux中&…

DAMA数据管理知识体系指南之数据操作管理

第6章 数据操作管理 6.1 简介 数据操作管理是结构化数据的开发、维护和支持的活动&#xff0c;使企业数据资源达到最佳的利用价值。数据操作管理包括两项子职能&#xff1a;数据库支持和数据技术管理。 数据操作管理的目标是&#xff1a; &#xff08;1&#xff09;保护和确保…

SpringBoot 如何保证接口安全?老鸟们都是这么玩的!

大家好&#xff0c;我是飘渺。 对于互联网来说&#xff0c;只要你系统的接口暴露在外网&#xff0c;就避免不了接口安全问题。如果你的接口在外网裸奔&#xff0c;只要让黑客知道接口的地址和参数就可以调用&#xff0c;那简直就是灾难。 举个例子&#xff1a;你的网站用户注册…