diff --git a/changelogs/2026-05/07_fix_admin_user_list_status_dict_whitelist.md b/changelogs/2026-05/07_fix_admin_user_list_status_dict_whitelist.md new file mode 100644 index 0000000..0679a6c --- /dev/null +++ b/changelogs/2026-05/07_fix_admin_user_list_status_dict_whitelist.md @@ -0,0 +1,72 @@ +# fix(user): /admin/user 列表非字典 status (status=1) 致 total > 0 records=0 + +**PR**: [#1834](https://git.1814.love:8443/wx/HL/pulls/1834)(主修复)+ #1831 + #1832(回滚于 #1833) **Issue**: [#1829](https://git.1814.love:8443/wx/HL/issues/1829) **合并**: 2026-05-07 + +通知对象: 无前端配合(后端 controller 防御兜底,前端可继续传任意值后端兜底) + +--- + +## 现象 + +正式 admin: `https://admin.1814.love/admin/user?page=1&pageSize=100&status=1` 显示「有数量没有数据」。 + +测试服真 admin token round-trip 完美复现: + +| 请求 | total | records | 备注 | +|---|---|---|---| +| `status=1` | **28** | **0** | ❌ BUG | +| 其他无效值(0/2/ABCXYZ/true) | 0 | 0 | ✅ 一致 | +| `status=ACTIVE` | 28 | 28 | ✅ | + +仅 `status=1` 这个特定值出 BUG。 + +## 根因(深度排查 3 轮才定位) + +MP `PaginationInnerInterceptor` 用 jsqlparser 把 records SELECT SQL parse 改成 count SQL。`status='1'` 这个字面值 + `.select(projection)` + `.ne(status, X)` + `.eq(status, '1')` 组合触发 jsqlparser 解析失败,**fallback 到 `SELECT COUNT(*) FROM admin_user`**(丢全部 WHERE)→ count=28(全表)records=0。 + +3 轮修复教训: +- **r1 PR #1831**: wrapper 从 `LambdaQueryWrapper` 迁 `LambdaQueryWrapperX` + Mapper default 方法。合 CODE_RULES 1.2,顺手修了 `status=""` 空串过滤。**但 status=1 BUG 仍在**。 +- **r2 PR #1832**: 试图 `selectCount(w) + setSearchCount(false) + selectPage(page,w)` 绕过 PaginationInnerInterceptor 自动 count。**炸 SQL 全 500 BadSqlGrammarException**(wrapper state mutation 引发)→ PR #1833 revert 紧急回滚。 +- **r3 PR #1834 (最终)**: 在 controller 层加 status 字典白名单 (`ACTIVE/LOCKED/DISABLED`),非字典值直接返 `PageResult.empty()` 不进 mapper。**从源头杜绝 MP BUG 路径**。 + +## 后端修了什么 + +`AdminUserController.listAdmins`(`hl-user-service/.../controller/AdminUserController.java`)在 role 鉴权之后、调 service 之前加: + +```java +private static final Set VALID_ADMIN_STATUS = Set.of( + UserConstants.STATUS_ACTIVE, + UserConstants.ADMIN_STATUS_LOCKED, + UserConstants.STATUS_DISABLED); + +if (status != null && !status.isEmpty() && !VALID_ADMIN_STATUS.contains(status)) { + return Result.success(PageResult.of(Collections.emptyList(), 0L, page, pageSize)); +} +``` + +mapper / service 0 改动。守护单测 `listAdmins_invalidStatus_returnsEmptyPage` 验证 status="1" 时 mapper 不被调用 + 响应空。 + +## 前端无需改动 + +前端可继续传任意值,后端兜底返空。但**建议前端只传字典值**(ACTIVE/LOCKED/DISABLED)避免无意义请求。 + +## 部署依赖 + +无 nacos / SQL / 第三方配置变更。直接部署即生效。 + +## 受影响范围 + +- `/admin/user` 列表所有 status 过滤路径(修复) +- 其他 GET 列表接口同样 PaginationInnerInterceptor 行为可能存在类似潜在 BUG,但本 PR 不范围内修复 +- 已识别遗留(本次未处理): MP `PaginationInnerInterceptor` jsqlparser fallback BUG 根因仍在,需要升级 MP 版本或换 SQL 方案,留后续 PR + +## 测试服 round-trip 验证 (PR #1834 部署后) + +``` +status=1 → total=0 records.length=0 ✅ +status=0/2/ABCXYZ/true → 0/0 ✅ +status=ACTIVE → 28/28 ✅ +status=DISABLED → 0/0 ✅ +不传 status → 28/28 ✅ +status=ACTIVE&roleId=1 → 7/7 ✅ +```