news 2026/4/3 3:50:44

MyBatisPlus SQL拦截器:监控所有对Token余额的操作语句

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MyBatisPlus SQL拦截器:监控所有对Token余额的操作语句

MyBatisPlus SQL拦截器:监控所有对Token余额的操作语句

在账户系统频繁变动的业务场景中,一次未被记录的余额修改可能引发连锁反应——用户投诉、财务对账不平、风控系统告警。尤其是在微服务架构下,多个服务模块都可能通过 Mapper 接口间接操作token_balance字段,传统的日志埋点方式早已力不从心:谁来保证每一处 update 方法都加了 audit log?哪个开发不会忘记在新增逻辑里补上监控代码?

这时候,我们需要一种“上帝视角”的能力:不依赖业务代码自觉上报,而是主动监听数据库层每一次触碰敏感字段的行为。而 MyBatisPlus 的 SQL 拦截器,正是实现这一目标的理想工具。


为什么选择 SQL 拦截器做审计?

设想这样一个场景:某个夜间任务批量重置用户 Token 余额,但没有走标准接口,而是直接调用了BaseMapper.update()。如果仅靠人工打日志,这条操作就会彻底消失在监管视野之外。但如果我们能在 ORM 层统一拦截所有 SQL 执行,并自动识别是否涉及关键字段,问题就迎刃而解。

MyBatisPlus 提供了基于 MyBatis 插件机制的InnerInterceptor接口,允许我们在 SQL 实际执行前介入处理流程。它本质上是一个运行在持久层的 AOP 切面,能够覆盖所有的增删改查操作,且完全无需改动现有业务逻辑。

这不仅意味着“零侵入”,更带来了全局一致性保障——无论代码是谁写的、走的是哪种调用路径,只要最终生成了操作token_balance的 SQL,就逃不过拦截器的眼睛。


拦截器是如何工作的?

MyBatis 的执行链条其实很清晰:

  1. 你调用userMapper.updateById(entity)
  2. MyBatis 根据 MappedStatement 生成 SQL
  3. Executor 开始执行前,触发注册的拦截器链
  4. 我们的自定义逻辑在这里拿到原始 SQL 和参数
  5. 判断是否需要审计,记录日志后放行

关键就在于这个“中间地带”。我们不需要去修改任何一个 Mapper 接口或 Service 方法,只需要把一个实现了InnerInterceptor的类注入到 Spring 容器,并注册进MybatisPlusInterceptor链中即可。

@Configuration @MapperScan("com.example.mapper") public class MyBatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new TokenBalanceSQLInterceptor()); return interceptor; } }

就这么简单。从此以后,整个应用中所有通过 MyBatisPlus 发出的 SQL 请求,都会先经过我们的审计关卡。


如何精准识别“动了余额”的操作?

最朴素的想法是:看看 SQL 里有没有token_balance这个字段名。听起来可行,但在真实环境中很容易误判或漏判。

比如:

UPDATE user_account SET balance_token = balance_token - 1 WHERE user_id = ?

这种写法字段名是balance_token,简单的字符串匹配就会失败。

再比如:

UPDATE user_account SET extra_info = ?, version = ? WHERE id = ?

其中extra_info是 JSON 字段,里面藏了"tokenBalance": 99—— 这种情况也不能算真正修改了余额字段。

所以,我们必须分层次处理:

第一层:快速关键词过滤

先做轻量级扫描,避免对每条 SQL 都进行复杂解析:

private static final String[] SENSITIVE_COLUMNS = {"token_balance", "balance_token", "token_amount"}; private boolean containsSensitiveColumn(String sql) { String lowerSql = sql.toLowerCase(); for (String col : SENSITIVE_COLUMNS) { if (lowerSql.contains(col)) { return true; } } return false; }

这一步可以在毫秒内完成,过滤掉 90% 不相关的 SQL。

第二层:语法树精确解析(可选)

对于命中初步判断的 SQL,可以使用 JSqlParser 解析成 AST,提取真正的 SET 字段列表:

try { Statement stmt = CCJSqlParserUtil.parse(sql); if (stmt instanceof Update) { Update update = (Update) stmt; for (Column column : update.getColumns()) { if (SENSITIVE_COLUMNS_SET.contains(column.getColumnName().toLowerCase())) { triggerAuditLog(); // 精准命中 break; } } } } catch (JSQLParserException e) { // 解析失败也不影响主流程,降级为原始匹配结果 }

虽然引入了解析开销,但由于只针对少数可疑 SQL 触发,整体性能依然可控。更重要的是,准确性大幅提升。


审计日志该记什么内容?

光知道“有人改了余额”还不够,我们必须能回溯上下文。一条合格的审计日志至少应包含以下信息:

字段示例说明
操作类型UPDATEINSERT / DELETE / SELECT
表名user_account来源于 MappedStatement ID 或 SQL 解析
影响字段token_balance被修改的具体列
执行 SQLUPDATE …清洗后的可读 SQL
Mapper 方法UserAccountMapper.updateBalance定位代码位置
参数摘要{userId: 10086, delta: -1}敏感数据需脱敏
时间戳2025-04-05T10:23:10.123Z精确到毫秒
调用线程http-nio-8080-exec-3协助排查并发问题

示例输出:

[AUDIT] Token balance operation detected Type: UPDATE, Table: user_account, Field: token_balance Mapper: com.example.mapper.UserAccountMapper.updateById SQL: UPDATE user_account SET token_balance = ? WHERE id = ? Params: [99, 10086] Timestamp: 2025-04-05T10:23:10.123Z Thread: http-nio-8080-exec-3

⚠️ 注意:不要在拦截器中直接打印大量日志!建议将事件封装为对象,异步发送至 Kafka 或写入独立审计文件,防止阻塞主线程。


实战中的设计考量与避坑指南

性能:快慢分离是关键

拦截器运行在核心执行路径上,任何耗时操作都会拖慢整个请求。因此必须坚持两个原则:

  1. 默认走轻量逻辑:只做字符串 contains 判断;
  2. 重操作异步化:如需入库或发消息,务必使用线程池或事件队列。
@Autowired private ApplicationEventPublisher eventPublisher; private void triggerAuditLog(AuditEvent event) { // 异步发布事件,由监听器处理落盘或推送 eventPublisher.publishEvent(event); }

准确性:表名不能靠猜

很多人习惯从MappedStatement.getId()反推表名,例如把UserAccountMapper.updateById映射为user_account表。但这只是命名约定,一旦打破就失效。

更好的做法是结合 MyBatis Plus 的TableNameHandler或直接缓存实体类与表名的映射关系:

@TableInfo(value = "wallet_info") // 实际表名 public class WalletEntity { ... }

或者使用 JPA 注解 + 反射提前加载元数据,在启动时建立映射表。

安全性:防止敏感信息泄露

SQL 中可能包含手机号、身份证等明感字段。记录日志前应对参数做脱敏处理:

private Object maskParam(Object value) { if (value instanceof String str) { if (PhoneUtils.isPhone(str)) { return PhoneUtils.mask(str); // 138****1234 } if (IdCardUtils.isIdCard(str)) { return IdCardUtils.mask(str); } } return value; }

也可以采用正则替换的方式对 SQL 字符串本身进行清洗。

可控性:必须支持动态开关

生产环境不可能永远开启全量审计。应该提供配置项控制功能启停:

audit: token-balance: enabled: true level: WARN # ALL / WRITE_ONLY / OFF include-tables: user_account,wallet_info

在拦截器中读取配置,决定是否执行检测逻辑:

@Value("${audit.token-balance.enabled:true}") private boolean enabled; @Override public void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) { if (!enabled) return; // ... }

这样即使出现问题也能快速关闭,不影响线上稳定性。


它还能用来做什么?

这套机制的价值远不止监控 Token 余额。稍作扩展,就能成为企业级数据安全基础设施的一部分:

  • 金额字段监控amount,price,fee等金融相关字段变更追踪;
  • 权限字段变更告警:如role_level,is_admin被修改时立即通知管理员;
  • 软删除保护:拦截对已标记删除记录的意外更新;
  • 冷热数据分离提示:当查询访问超过 N 个月的历史订单时记录警告;
  • 防误删机制:禁止执行无 WHERE 条件的 DELETE 操作(可在拦截器中抛异常阻止);

甚至可以构建一个统一的SensitiveFieldAuditInterceptor,通过注解标记需要监控的字段:

@SensitiveField(audit = true, reason = "account balance") private Integer tokenBalance;

然后在拦截器中读取实体类元数据,动态生成监控规则。


结语

数据安全不是事后补救,而是事前设防。MyBatisPlus 的 SQL 拦截器给了我们一把“显微镜”,让我们能看到每一笔数据库操作的真实面目。

当你不再依赖开发者的自律来保证日志完整性,当你能在第一时间发现非正常路径的余额变更,当合规审计不再是令人头疼的手工台账——你就知道,这种底层可观测性的建设,有多么值得。

这不是炫技,而是一种工程上的成熟:用最小的代价,换取最大的确定性。而那句老话依然成立——最好的监控,是让人意识不到它存在,却又无处不在

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/30 16:32:09

飞书文档批量导出终极指南:快速完成知识库完整迁移

飞书文档批量导出终极指南:快速完成知识库完整迁移 【免费下载链接】feishu-doc-export 项目地址: https://gitcode.com/gh_mirrors/fe/feishu-doc-export feishu-doc-export是一款专为飞书用户设计的文档批量导出工具,支持Windows、Mac、Linux三…

作者头像 李华
网站建设 2026/3/10 1:51:34

Screen Translator完整使用指南:零基础快速掌握屏幕翻译技巧

Screen Translator完整使用指南:零基础快速掌握屏幕翻译技巧 【免费下载链接】ScreenTranslator Screen capture, OCR and translation tool. 项目地址: https://gitcode.com/gh_mirrors/sc/ScreenTranslator 还在为阅读外文资料而困扰吗?Screen …

作者头像 李华
网站建设 2026/3/28 9:22:59

如何检测RS485总线空载与短路状态:完整示例

如何准确判断RS485总线是否短路或空载?一套低成本高可靠的实战方案在工业现场,你有没有遇到过这样的场景:设备突然“失联”,通信中断,排查半天发现是RS485总线被施工人员误接、端子松动,甚至A/B线直接短在一…

作者头像 李华
网站建设 2026/3/28 22:55:20

400 Bad Request常见于Header缺失?修复DDColor客户端请求头

400 Bad Request常见于Header缺失?修复DDColor客户端请求头 在AI图像修复应用日益普及的今天,越来越多用户通过可视化工具如ComfyUI为老照片“上色”。一个典型的场景是:上传一张黑白旧照,点击“运行”,期待几秒后看到…

作者头像 李华
网站建设 2026/3/27 16:49:04

自动化测试必备:ChromeDriver模拟用户操作DDColor Web界面

自动化测试必备:ChromeDriver模拟用户操作DDColor Web界面 在AI图像修复工具日益普及的今天,如何确保一个基于深度学习的Web应用——比如老照片上色系统——在每次更新后依然稳定可用?这不仅是开发者的日常挑战,更是MLOps实践中绕…

作者头像 李华
网站建设 2026/3/31 4:27:36

AI图像修复商业化路径:通过DDColor引流推广GPU算力服务

AI图像修复商业化路径:通过DDColor引流推广GPU算力服务 在家庭相册里泛黄的黑白老照片前驻足,是许多人共同的情感记忆。而今天,一张尘封数十年的旧照,只需十几秒就能重焕色彩——这不是魔法,而是AI正在悄然改变我们与历…

作者头像 李华