# fix: /mp/customizer/me 错把非定制师 admin 当定制师 + 网关白名单 + sharerOpenid 空串 (P0)

> **仓库**: HL (后端 hl-user-service + hl-mp-service + hl-gateway + hl-order-service-v2)
> **关联 PR/Issue**: PR #2923, Closes #2922
> **日期**: 2026-05-22
> **影响范围**: `/mp/customizer/me` 接口 + 客人下单 sharerOpenid 反查 + 网关白名单
> **接收方**: mmg (前端)
> **前端**: **可能需要调整** — 之前依赖 `/mp/customizer/me` 给非定制师 admin 显示分享按钮的行为现在不再生效(本就是 bug),前端逻辑无需改

---

## 🚨 关键修复

### 1. P0: 非定制师 admin 被错判成定制师

`GET /mp/customizer/me` 之前的实现 (`#2907` 引入) 仅过滤 `admin_user.mobile + status=ACTIVE`,**不校验 CUSTOMIZER 角色**。导致:

- 运营/财务/客服等**任意活跃 admin** 用本人手机号 SMS 登录 C 端小程序,前端会看到「分享」按钮
- 转发后客人下单时被 `CustomizerValidator` 角色校验拒绝
- **订单悄悄给随机定制师**,前端无任何感知
- 生产风险: 分润纠纷 / 「为啥我分享了订单没给我」客诉

**修复**:`AdminUserService.getCustomizerByMobileForFeign` 加 CUSTOMIZER 角色过滤,非定制师 admin 直接返 null。

**联动收益**: `OrderCreateService.resolveSharedCustomizerId` 反查链路也自动受益,sharerOpenid 反查到的 admin 必然是定制师身份。

### 2. P1-2: `/mp/customizer/me` 未登录直接 401 卡 UI

`JwtAuthFilter.OPTIONAL_AUTH_URLS` 之前只含 `/mp/product/*` `/mp/explore/*`,**`/mp/customizer/me` 走 MANDATORY** 未登录直接 401,跟 `MpCustomizerController.userId == null` 兜底分支设计冲突。

**修复**: 加 `/mp/customizer/me` 到 OPTIONAL_AUTH_URLS,未登录返 `isCustomizer: false`。

### 3. P1-4: sharerOpenid 空串导致下单 400

`MpCreateOrderRequest.sharerOpenid` + `MpOrderSaveReqVO.sharerOpenid` `@Pattern("^[a-zA-Z0-9_-]+$")` 不接受空串。前端从 `onLaunch.options.sharerOpenid` 拿值,**误传 `""` 整个下单 400**。

**修复**: `@Pattern` 改 `^$|^[a-zA-Z0-9_-]+$` 接受空串。

---

## 一、API 行为变化

### `GET /mp/customizer/me`

| 调用方 / 场景 | 之前 | **现在** |
|---|---|---|
| 未登录 | 401 Missing Authorization | **200 + `{isCustomizer: false, adminId: null, customizerName: null, customizerAvatar: null}`** |
| 已登录但 `user.phone` 为空 | 同左 | 同左 (`isCustomizer: false`) |
| 已登录, `user.phone` 在 admin_user 找到对应 ACTIVE admin **但不是 CUSTOMIZER 角色** | **错误返 `isCustomizer: true` + 该 admin 信息** (P0 bug) | **`isCustomizer: false`** (修复后) |
| 已登录, `user.phone` 对应有 CUSTOMIZER 角色的 admin | `isCustomizer: true` + admin 信息 | 同左 |

### `POST /mp/order/create`

| 场景 | 之前 | 现在 |
|---|---|---|
| `sharerOpenid` 字段传 `""` 空串 | 400 Bad Request `@Pattern 不匹配` | **200 OK** (走兜底 `assignRandomCustomizer`) |
| `sharerOpenid` 传定制师 openid | 反查 user.phone → admin 任意活跃 admin (P0 bug) | **反查仅 CUSTOMIZER 角色 admin**,非定制师 sharerOpenid 走兜底 |

---

## 🚨 前端需要做的事

### 1. 不再依赖 P0 bug

之前如果前端依赖"非定制师 admin 也能看到分享按钮"的行为 (这本身是 bug),现在该行为修正。前端按 `data.isCustomizer` 字段渲染即可,**逻辑无需改**。

### 2. 可选: 未登录场景预渲染

由于 `/mp/customizer/me` 现在支持未登录调用,**分享落地页 (客人从分享链接打开小程序首页) 可以在未登录态调一次** `/mp/customizer/me`,根据返回的 `isCustomizer: false` 跳过分享按钮渲染。这是设计原意。

### 3. sharerOpenid 字段值标准化(建议)

前端尽量保证 `sharerOpenid` 字段为 `string | null` 而不是空串。空串虽然现在不再卡 400,但语义不规范。

### 4. 自查

```bash
grep -rE "isCustomizer|sharerOpenid|/mp/customizer/me" hl-ui/src mp-cli/src
```

---

## 二、测试服验证记录

部署 user + mp + gateway 三服务到测试服 (2026-05-22 22:45) 后真测:

| # | 场景 | 实测 |
|---|------|------|
| T1 | CUSTOMIZER 角色 admin SMS 登录 → /mp/customizer/me | ✅ `isCustomizer: true, adminId, customizerName` |
| T2 | 非 CUSTOMIZER admin (运营/客服) SMS 登录 → /mp/customizer/me | ✅ `isCustomizer: false` (P0-1 修复后正确拦截) |
| T3 | 未登录直接调 /mp/customizer/me | ✅ 200 + `isCustomizer: false` (P1-2 网关 OPTIONAL 加白生效) |

---

## 三、不在本期范围 (留尾巴另起工单)

- P0-2 `AdminUserService` 注入 `WechatUserMapper`(类头部已备案,历史包袱)
- P1-3 `selectAnyByPhone` `LIMIT 1` 无 ORDER BY(业务唯一性约束,概率极低)
- P1-5 反查 Feign 重复调用合并优化(P99 未观察)

---

## 四、联系人

后端: wx(呼籁旅行)
前端: mmg

如有疑问可在 [#2922](https://git.1814.love:8443/wx/HL/issues/2922) 评论区留言。
