MoreRSS

site iconSunZhongWei | 孙仲维 修改

博客名「大象笔记」,全干程序员一名,曾在金山,DNSPod,腾讯云,常驻烟台。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

SunZhongWei | 孙仲维 的 RSS 预览

一例 Redis 导致服务器 CPU 飙高的问题排查

2026-08-19 21:56:59

昨晚 10 点,一个网站的负载飙高,触发了 uptime 的宕机报警。从 top 命令结果里看,redis 进程 cpu 飙高,但是我起初以为是被恶意攻击了,把 cloudflare 防护规则严格了一些,临时负载降下来了。由于太困,明天还有事情,就没有继续观察。

早上 6 点起来,又看了,负载还是高。但是并不影响 golang 相关的服务,只有 php 的旧站点受到了影响。
哎,PHP 这些破玩意真糟心。牛马还是要上班,就只能寄希望于晚上回来解决了。

晚上捣鼓了两个小时,算是解决了。

查看 redis 运行状态

redis 放在 docker 里,所以登录 redis 使用:

docker compose exec redis redis-cli

查看运行状态:

> INFO stats

total_connections_received:168608
total_commands_processed:11273966
instantaneous_ops_per_sec:326
total_net_input_bytes:203350837911
total_net_output_bytes:26614034188264
total_net_repl_input_bytes:0
total_net_repl_output_bytes:0
instantaneous_input_kbps:31.01
instantaneous_output_kbps:453887.44
instantaneous_input_repl_kbps:0.00
instantaneous_output_repl_kbps:0.00
rejected_connections:0
sync_full:0
sync_partial_ok:0
sync_partial_err:0
expired_keys:189148
expired_stale_perc:0.36
expired_time_cap_reached_count:0
expire_cycle_cpu_milliseconds:22144
evicted_keys:0
evicted_clients:0
total_eviction_exceeded_time:0
current_eviction_exceeded_time:0
keyspace_hits:7151109
keyspace_misses:2783287
pubsub_channels:0
pubsub_patterns:0
pubsubshard_channels:0
latest_fork_usec:11041
total_forks:245
migrate_cached_sockets:0
slave_expires_tracked_keys:0
active_defrag_hits:0
active_defrag_misses:0
active_defrag_key_hits:0
active_defrag_key_misses:0
total_active_defrag_time:0
current_active_defrag_time:0
tracking_total_keys:0
tracking_total_items:0
tracking_total_prefixes:0
unexpected_error_replies:0
total_error_replies:3546
dump_payload_sanitizations:0
total_reads_processed:11920639
total_writes_processed:18558028
io_threaded_reads_processed:0
io_threaded_writes_processed:0
reply_buffer_shrinks:123859
reply_buffer_expands:375511
  • 输入带宽 (instantaneous_input_kbps):31.01 KB/s(请求很小)
  • 输出带宽 (instantaneous_output_kbps):453887.44 KB/s ≈ 443 MB/s(响应巨大)

结论:输入和输出的差距高达 1.4万倍。这说明客户端只发了一个很小的请求(比如 HGETALL 或 SMEMBERS),但Redis返回了一个极其庞大的数据给客户端。

查看当前哪个客户端的输出缓冲区占用最大

CLIENT LIST

输出:

id=112411 addr=[2400:8901::7]:41764 laddr=[2400:8901::4]:6379 fd=21 name= age=28381 idle=0 flags=N db=2 sub=0 psub=0 ssub=0 multi=-1 qbuf=0 qbuf-free=20474 argv-mem=0 multi-mem=0 rbs=4096 rbp=4096 obl=0 oll=1 omem=20971544 tot-mem=20996888 events=rw cmd=get user=default redir=-1 resp=2
id=112430 addr=[2400:8901::7]:52090 laddr=[2400:8901::4]:6379 fd=26 name= age=28366 idle=0 flags=N db=2 sub=0 psub=0 ssub=0 multi=-1 qbuf=0 qbuf-free=20474 argv-mem=0 multi-mem=0 rbs=16384 rbp=16384 obl=0 oll=1 omem=20971544 tot-mem=21009176 events=rw cmd=get user=default redir=-1 resp=2

有 3个客户端连接 正在拉取 超大Key,每个连接的输出缓冲区(omem)都达到了 20,971,544 字节(约 20 MB).这三个连接都在执行 cmd=get,且都在 db=2(数据库2),说明它们都在 反复读取同一个超大字符串类型的Key(或者内容完全相同的大Key)。每个响应包约 20MB,三个连接同时输出,瞬时输出带宽自然飙到 450MB/s。

找到这个大 KEY

连 redis db 2:

执行完后,它会告诉你哪个数据类型的Key最大(比如最大的Hash有多大)。

> docker compose exec redis redis-cli -n 2 --bigkeys -i 0.1

Sampled 4658 keys in the keyspace!
Total key length in bytes is 297586 (avg len 63.89)

Biggest string found '"9c6a2e0bcbb03f32fb0382d38fa00bbe-cache-com_modules-5cdbee63315b6b81941ea5c4844ffd8c"' has 26627066 bytes

-i 0.1 表示每扫描100个Key就休息0.1秒,防止在高负载时加剧CPU抖动。

删除这个大 KEY

docker compose exec redis redis-cli -n 2 DEL "9c6a2e0bcbb03f32fb0382d38fa00bbe-cache-com_modules-5cdbee63315b6b81941ea5c4844ffd8c"

cpu 就正常了。但是只正常了半个小时,然后这个 key 又变大到了 15M。。。

删了几次,还是反复。我无语了。。。

gemini 救命

改问 gemini 之后,找到了根源:

joomla 的 redis 缓存中,这个 key : cache-com_modules 一直暴涨,有 20 多M,这是为什么

在 Joomla 中,cache-com_modules 这个 key 专门用于存储网站所有模块(Modules)的缓存数据(即模块渲染后的 HTML 代码及相关参数)。

这个 key 的体积暴涨到 20 多兆(在 Redis 这种内存数据库中,对于单一文本 key 来说已经非常庞大),通常是由以下几个核心原因导致的:

开启了“渐进式缓存”(Progressive Caching)—— 最常见原因
Joomla 的全局缓存分为两种模式:保守式(Conservative)和渐进式(Progressive)。

  • 保守式缓存: 按模块生成缓存。1000 个未登录访客访问同一个页面,共用同一份模块缓存。
  • 渐进式缓存: 为每一个独立访客(基于 Session)单独生成一份模块缓存。

如果你的网站开启了“渐进式缓存”,当有大量的搜索引擎爬虫(蜘蛛)、恶意扫描机器人或大量真实散客访问时,Joomla 会为每一个全新的 Session 生成一份完全独立的模块缓存。这些缓存全部堆积在 com_modules 下,导致该 key 的体积呈指数级暴涨。

切换回“保守式缓存”. 这是最立竿见影的解决方法:

  • 进入 Joomla 后台 -> 系统 (System) -> 全局设置 (Global Configuration)。
  • 点击 系统 (System) 选项卡。
  • 在 缓存设置 (Cache Settings) 中,将“系统缓存”修改为 保守式缓存 (Conservative caching)。
  • 保存设置。

这个 key 的过期时间

# docker compose exec redis redis-cli -n 2 TTL "9c6a2e0bcbb03f32fb0382d38fa00bbe-cache-com_jmap-9667e9aa5d8b93f1cb1835bd4315b7c9"
(integer) 3456

返回结果 (integer) 3456 表示该 Key 的剩余存活时间为 3456 秒,换算一下大约是 57.6 分钟(接近 1 小时)。

看来不需要我加定时任务自动清理了。

Markdown 文件转 Word 格式,想设置个好看的模板居然是世界性难题

2026-08-18 19:45:11

这周写了个软件开发需求文档,用 Markdown 格式写的,主要是为了方便编辑,也方便 AI 去补写。

但是转 word 居然变成了难题。试了几种方案:

workbuddy

我第一次用的时候,不但让 workbuddy 把 markdown 转换成 word 格式,同时修改一下文法,改成汇报的文风。

workbuddy 的处理逻辑是,先把 markdown 转换成 HTML,然后再基于 HTML 转换成 docx。但是,在二级标题的处理上,加了不兼容的样式,非常影响美观度。整个过程还采用了 Python 脚本,不太专业,之前提过 软著自动生成 Skill 最好的方案是

.NET SDK 8.0+:用于启用更完整的 DOCX OpenXML 生成和校验能力。Python 方案只能作为备用

Python 生成 word 的样式还是不太完美。整个过程异常耗时,耗 token。毫无意义。

飞书文档

飞书文档支持直接 ctrl v 黏贴进去 markdown,然后导出 word。

我也是用的这个方案,但是太朴素了,我自己用其实没啥问题。但是领导嫌弃太简陋。确实有点。。。

我又用 office word 和 WPS 分别试了一下美化,哎,都是鸡肋,连个 AI 美化都做不好。

可行方案

我觉得,有两个方案可以试试:

  1. 这种文档一开始就用 word 模板从头编辑,不要考虑 markdown 转 docx 的方式。
  2. 我实现一个 markdown 转 HTML 的美化工具,自己控制样式,总比 workbuddy 那种 python 脚本强。然后 html 转 word。

虽然已经进入了离开了 AI 就没法办公的时代,但是这种常见其实浪费 token 毫无意义,还不如自己写的工具靠谱。

给领导写汇报的 AI Skill

2026-08-16 16:55:26

很多年没有给领导写过项目开发汇报文件了,毕竟之前都是直接开干,也不需要程序员去评估。在写了一版之后,发现遗漏了不少关键点,例如:服务器成本、认证成本、每个功能的备选方案、工期能否砍一半等。为了以后能更高效地输出汇报文件,我决定生成一个 AI Skill,帮我去自动排查汇报中是否遗漏。

原则

虽然是给领导看的,但是我觉得最重要的是这个文档对自己有价值。把冠冕堂皇的内容放在前面,应付完之后。后面还是需要记录对自己开发有价值的内容,例如,用户角色分析、用户故事、需要提前申请的资源等。

至于设计稿,我感觉意思一下就行了,要完整出所有界面,也挺耗费精力的。也耗费 token 😅。

时间控制在一个小时之内,不用花费太多时间在上面。毕竟领导可能看不了1分钟。同时,不要给子标题添加标号,因为随时会调整顺序。

问 AI

首先我问了 DeepSeek 一个问题:

给领导写软件项目研发计划汇报,需要突出哪些要素

我觉得回答非常精炼:

三个要点

如果时间有限,只能讲3分钟,请死磕这3点:

  • 什么时候能上线?(给领导吃定心丸)工期能否砍一半。。。内含整体排期
  • 要花多少钱/人?(让领导算账)包括:服务器成本、认证成本等。内含分工。正式一点叫“资源与预算”
  • 最大的坑是什么,需要我(领导)怎么帮你摆平?(把领导拉入伙,而非让他当裁判)这些坑是否有平替方案。用坑做标题有点太随意了,还是叫“风险与应对”吧。这个“应对”这个词非常好,正好引入平替方案部分。

话糙理不糙。我决定就用这三个作为二级标题了,放在文档最前面。

下面是 AI 给出的所有内容。值得借鉴:

给领导汇报软件项目研发计划,最核心的一点是:不要把自己当作“程序员”去汇报技术细节,而要把自己当作“CEO”去汇报如何达成商业目标。

领导(尤其是高层)最关心的是投资回报率、风险控制和节奏感。以下是需要重点突出的六大核心要素,以及具体的表达话术:

1. 价值导向(Why:为什么要做?)

不要一上来就讲“我们要用React还是Vue”,要先讲业务价值

  • 突出要素: 解决什么痛点、带来什么增量、提升多少效率。
  • 汇报话术: “本项目的核心价值在于将订单处理效率提升 30%,预计每年节省人力成本 XX万。本次研发计划的所有排期,均围绕‘尽快验证该核心价值’展开。”

2. 里程碑节点(When:什么时候做完?)

领导最怕“遥遥无期”。不要只给一个最终截止日期,要设置明确的“关键里程碑”。

  • 突出要素: 阶段划分(如:需求确认、UI定稿、核心功能开发、联调测试、UAT验收)。
  • 汇报技巧: 采用倒排工期,并明确“不可压缩”“可并行”的节点。建议用甘特图路线图(Roadmap)展示,让领导一眼看清节奏。

3. 资源与预算(Who & How much:需要什么?)

这是领导签字的核心依据。要明确说出缺口边界

  • 突出要素: 需要的人数、特殊硬件、服务器费用、外部采购成本。
  • 汇报话术: “现有团队可支撑基础开发,但前端缺口2人,需要外部招聘或借调,否则周期将延长 2周。服务器预算在现有额度内,无需额外申请。”

4. 风险与应对(What if:出问题了怎么办?)

这是展现你成熟度的地方。只提进度不提风险,在领导看来等于“立军令状却不设退路”。

  • 突出要素: 技术难点(如高并发)、人员变动风险、第三方接口不稳定。
  • 核心技巧: 每提出一个风险,必须紧跟一个“预案”
    • 错误: “这个接口可能很慢。”
    • 正确: “若第三方接口延迟超2秒,我们将启用本地缓存方案作为兜底,保证用户体验。”

5. 沟通与决策机制(How to communicate:怎么汇报?)

向领导表明你已建立有序的管控体系,让他放心

  • 突出要素: 周报/日报机制、变更审批流程、例会时间。
  • 汇报话术: “每周五下午4点发送项目简报,进度偏差超过 10% 将立即触发红灯预警并召开紧急会议,确保风险不过夜。”

6. 舍弃清单(What is out of scope:这次不做哪些?)

这是很多汇报中容易忽略的。明确“不做”,能有效避免领导在中途随意追加需求,保护项目不延期。

  • 突出要素: 本次版本不包含的功能、非功能性需求(如只兼容主流浏览器,不考虑IE)。
  • 汇报话术: “为保证 3月1日 上线,本次V1.0版本暂不包含‘报表导出’和‘深色模式’,这些将列入 V2.0迭代计划。若必须加入,上线日期将相应顺延。”

附:汇报PPT/文档的结构建议(领导视角的逻辑)

  1. 项目背景(1页):一句话说清为什么要做。
  2. 核心目标与衡量指标(OKR)(1页):怎样算成功?(如:日活破10万)。
  3. 整体排期(Roadmap)(1页):核心! 建议用“时间轴+泳道”展示。
  4. 组织架构与分工(1页):谁负责什么,有事找谁。
  5. 资源需求(1页):缺什么,什么时候缺。
  6. 风险与依赖(1页):需要领导提供什么支持?(这是给领导分配任务的地方)。
  7. 后续沟通机制(1页):汇报节奏。

特别提示:如果时间有限,只能讲3分钟,请死磕这3点:

  1. 什么时候能上线?(给领导吃定心丸)
  2. 要花多少钱/人?(让领导算账)
  3. 最大的坑是什么,需要我(领导)怎么帮你摆平?(把领导拉入伙,而非让他当裁判)

最后记得: 汇报结尾一定要给出明确的“决策请求”——比如“以上计划请领导审批,如无异议我将按此启动。”——这样能直接推动项目进入执行阶段。祝汇报顺利!

Cloudflare 设置了网站缓存规则,但是依旧返回 pragma no-cache HTTP 头

2026-08-12 20:39:49

例如在 Cloudflare 的页面缓存规则中,设置了 3 天,但是返回的网页 HTTP 头是:

Cloudflare 页面返回

  • age 73132
  • alt-svc h3=“:443”; ma=86400
  • cache-control max-age=7200
  • cf-cache-status HIT
  • cf-ray a29e93ee2b8ca094-FRA
  • content-type text/html; charset=utf-8
  • date Wed, 12 Aug 2026 09:50:48 GMT
  • expect-ct max-age=86400, enforce
  • expires Wed, 17 Aug 2005 00:00:00 GMT
  • last-modified Tue, 11 Aug 2026 13:14:29 GMT
  • pragma no-cache

pragma no-cache 代表什么

pragma: no-cache 代表这是一个 HTTP/1.0 协议的遗留指令,其作用是要求缓存服务器在返回内容前,必须向源服务器进行验证(即强制回源验证)。

但在当前的场景中,它基本不起作用,可以忽略。因为现代浏览器和 Cloudflare 优先遵循 HTTP/1.1 的 Cache-Control 头。

现代标准以 Cache-Control 为准。如果同时存在,客户端会忽略 Pragma。

3 天 VS 2 小时

cache-control: max-age=7200(源站要求缓存 2 小时)

age: 73132(当前资源已在 Cloudflare 缓存中存了 73132 秒 ≈ 20.3 小时)

age 远大于 max-age,这证明了 Cloudflare 的“边缘缓存 TTL”(设置的 3 天/259200 秒)成功覆盖(覆盖/忽略)了源站的 max-age=7200。缓存规则已经完全生效,Cloudflare 强制将这个页面保留了将近一天,并且还会继续保留到 3 天期满。

Joomla 配置

pragma: no-cache 常由 Joomla 的系统缓存设置产生。关闭它可能从源头解决问题。

  • 登录 Joomla 管理员后台。
  • 进入 系统 (System) -> 全局配置 (Global Configuration)。
  • 点击 系统 (System) 选项卡。
  • 将 系统缓存 (System Cache) 设置为 “关闭 - 不缓存” (OFF - Don’t cache)。
  • 保存设置并清除 Joomla 缓存。

Joomla 修改源码

如果没有权限修改 Joomla 后台配置,可以在服务器上直接修改源码:

libraries/vendor/joomla/application/src/AbstractWebApplication.php

搜索 no-cache 并注释掉:

 if (!$this->allowCache()) {
    // Expires in the past.
    $this->setHeader('Expires', 'Wed, 17 Aug 2005 00:00:00 GMT', true);

    // Always modified.
    $this->setHeader('Last-Modified', \gmdate('D, d M Y H:i:s') . ' GMT', true);
    $this->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0', false);

    // HTTP 1.0
    $this->setHeader('Pragma', 'no-cache');
}

2026 Joomla SP Page Builder 漏洞, 允许未授权用户上传恶意 PHP 代码

2026-08-11 21:49:20

又中招了,GTranslate 服务说网站请求量过大,停止了服务。查看首页,发现一个首页,加载了 1000 多个 css 文件。

发现是 Joomla SP Page Builder 的漏洞,会通过 POST 上传一堆文件。

SP Page Builder在2026年被曝出多个严重安全漏洞(如CVE-2026-48908、CVE-2026-66494等),允许未授权用户上传恶意文件甚至执行代码

然后影响首页加载很多 css 文件。好在,之前添加了 nginx 规则,会阻止 media 路径下的所有代码执行。所以,只会看到加载了一千多个 css 文件,但是没有其他影响。

晚上,清理了这些配置,并且增加了 cloudflare 规则,阻止了 SP Page Builder 相关的 post 请求。

Nginx 日志

POST /index.php?option=com_sppagebuilder&task=asset.uploadCustomIcon 200

GET /media/com_sppagebuilder/assets/iconfont/icorlycgq/fonts/fawvfmj.PHP?t=ixbszjbawvencrtq&c=echo+SPPB-RCE-%24%28%287%2A6%29%29 HTTP/1.1 403 

攻击 IP 来自新加坡的 194.233.86.244。上传完成后,就开始尝试执行恶意 PHP 代码,好在我阻止了 media 下的 PHP 代码执行。

cloudflare 拦截规则

当请求是 POST 时,并且

http.request.uri.query contains "option=com_sppagebuilder"

就直接拦截。不用 Nginx 拦截的原因是,我不知道怎么设置 nginx 判断这种多条件的情况。而且用 cloudflare 也更节省服务器资源。

测试

确认是否禁止了 com_sppagebuilder 相关的 POST 请求。

curl -X POST "http://your-domain.com/index.php?option=com_sppagebuilder&task=asset.uploadCustomIcon" -I

如果返回 HTTP/2 403,说明拦截成功了。

清理首页加载项

即便清空了服务器 /media/com_sppagebuilder/assets/iconfont/ 下的恶意文件目录。首页依然会加载一堆 css,虽然报 404 也非常讨厌。

需要到数据库的 xxx_sppagebuilder_assets 数据表,清理掉里面的恶意文件配置。

枯燥的工作需要 SKILL 调剂,Spring Boot 中 Mybatis SQL 单元测试 SKILL

2026-08-10 19:59:07

枯燥的 MES 重构工作,感觉没有尽头,调教 SKILL 反而成了唯一的乐趣。

背景

Spring Boot 功能代码开发完成后,可以让 AI 自动编译,通过编译日志来排查是否有问题,然后自动修复。
但是,对于 Mybatis XML 文件中的 SQL 代码,在编译时通常排查不出问题,只有执行时才能发现数据库报错。例如:

Error querying database. Cause: java.sql.SQLSyntaxErrorException: ORA-01722: 无效数字

或者

Error querying database. Cause: java.sql.SQLDataException: ORA-01861: 文字与格式字符串不匹配

这些都是 AI 在实现代码时,经常会制造的一些 bug。虽然根源是没有把数据库表结构喂给 AI。

但是,我依然想实现一个 Skill 能自动发现 Mybatis XML 文件中的 SQL 代码的问题,然后自动修复,省去了我来回在界面里点击,再把错误信息复制给 AI,如此反复的过程。

需求

这个 Skill 可以对指定功能的 mybatis xml 文件中的 SQL 做单元测试。

H2 数据库还是 Oracle 数据库

如果要模拟数据库操作,是使用 H2 数据库(内存数据库,支持各种数据库的 SQL)还是 Oracle 数据库呢?

因为 SQL 强依赖 Oracle 专有语法(DECODE/TO_DATE)和真实表数据,只有连真实库才能真正验证。最后还是决定使用测试环境的 Oracle 数据库。

create skill prompt

这个 skill 接收功能名称,判断指定功能是否已经有代码实现了,如已实现,则自动创建对应的 mybatis xml 相关的 SQL 测试案例。如果已经有了单元测试,则按照该 skill 的最新标准修正单元测试。

需要通过 python (oracledb 库)脚本连接测试数据库(jdbc:oracle:thin:@//x.x.x.x:1521/testdb),来判断相关表的表结构、字段类型。并插入测试数据,注意,只能使用测试数据库执行。绝对不可以连接正式生产数据库来执行上面的操作。

同时把对应的表结构整理到 docs\database\tables.md 中, 使用 markdown 格式,二级标题是表名及对应的中文名,内容是表结构或者建表语句的 code block。目的是方便后续开发,或者调试 bug 时可以参考。

写入的测试数据,不需要清理,因为测试数据库是独立的,不会影响生产环境。

然后执行测试,如果测试通过,则提示测试通过,否则提示测试失败,并输出失败的测试案例名称。

测试需要覆盖到各种前端查询条件的场景,即,各种前端查询条件的场景,需要写一个测试案例,测试案例名称是功能名称 + 前端查询条件的名称。

测试流程

  • 先实现一个单元测试示例。确定方案选型
  • 实现第一版自动化测试 skill
  • 基于 skill 完善之前实现的单元测试示例

意外惊喜

AI 在编写 Skill 时,实际运行试点测试时,测试基础设施精确复现了你之前报告的 ORA-01861 错误——测试库有数据时 sn.created_at >= TO_DATE(?, ‘yyyy-MM-dd hh24:mi:ss’)(created_at 是 VARCHAR2)抛”文字与格式字符串不匹配”。这正验证了本 skill 的核心价值:真实测试库 + 有数据的表才能暴露空表单测发现不了的 SQL 问题。测试失败案例名为 queryByTimeRange。根因与修复已在 CODEBUDDY.md 记录(去掉 TO_DATE 改字符串比较)。

需要能自动修复

由于 plan 约束”不得改动业务源码”,我没有擅自修改 XML。需要我修复这个业务 XML 的 TO_DATE bug 吗? 修复后时间条件测试即可通过,年周号自检记录 在真实库的时间查询功能也将恢复正常。

修改 skill 的规则,如果发现测试失败,则自动修复,允许改动业务源码。

只运行单个测试类

mvn -pl ruoyi-admin test "-Dtest=SomeFunctionMapperTest" "-Dskip.tests=false"

运行某个包下的所有测试

mvn -pl ruoyi-admin test "-Dtest=com.sunzhongwei.somefunction.*" "-Dskip.tests=false"

运行所有 Mapper 测试

mvn -pl ruoyi-admin test "-Dtest=*MapperTest" "-Dskip.tests=false"

为何没有使用 MyBatis-Spring-Boot-Starter-Test

https://mybatis.org/spring-boot-starter/mybatis-spring-boot-test-autoconfigure/zh_CN/index.html

开发环境的版本: mybatis-plus-boot-starter 3.5.1

1. 项目用了 Dynamic Datasource(多数据源)

当前 RuoYi 项目生产配置用的是 dynamic-datasource-spring-boot-starter@DS 注解切数据源)。@MybatisTest 的自动配置期望的是单一 spring.datasource.* 数据源 + 默认的 SqlSessionFactory。而 dynamic-datasource 会自定义 DataSource 代理和 SqlSessionFactory,两者容易互相干扰——这也是为什么之前手工方案里特意用 DriverManagerDataSource 绕开 Druid/dynamic-datasource。

2. MyBatis-Plus 的全局配置兼容性

项目用的是 MyBatis-Plusmybatis-plus-boot-starter),而 mybatis-spring-boot-starter-test@MybatisTest 自动配置是基于原生 MyBatisSqlSessionFactory 来探测和加载的。官方 starter 对 MP 的兼容不是开箱即用,需要额外配置(如指定 MybatisSqlSessionFactoryBean、全局 MybatisConfiguration 等)。当前方案用的 MybatisSqlSessionFactoryBean + setTypeAliasesPackage 正是为了兼容 MP。

3. 类型别名(SysConfig 等)需要手工指定

@MybatisTest 默认不扫描 MP 的 typeAliasesPackage,之前我们踩过 SysConfig 别名解析失败的坑,手工配置里通过 setTypeAliasesPackage("com.sunzhongwei.domain") 明确解决了。