fix(iam): 合伙人登录双拦截——子账号status=DISABLED 与 主账号bindingStatus=PAUSED 均拒绝登录
CI / verify (push) Has been cancelled

This commit is contained in:
2026-08-17 23:39:53 +08:00
parent 5ca9aa00aa
commit 48d6900ab6
2 changed files with 50 additions and 15 deletions
@@ -304,12 +304,34 @@ export class AuthService {
}
}
/**
* 合伙人账号可登录态校验(统一闸门):
* 1) 账号自身 status !== ACTIVEDISABLED,含子账号)→ 拦截
* 2) 所属主账号 bindingStatus !== ACTIVEPAUSED,城市合伙人绑定暂停)→ 拦截
* 子账号的绑定状态以父主账号为准。
*/
private async assertPartnerAccountActive(account: {
id: bigint;
status: string;
isPrimary: number;
bindingStatus?: string | null;
}): Promise<void> {
if (account.status !== 'ACTIVE') {
throw new BadRequestException('该账号已暂停使用,请联系客服人员');
}
const primary =
account.isPrimary === 1 ? account : await this.resolvePrimaryAccount(account.id);
if (primary.bindingStatus && primary.bindingStatus !== 'ACTIVE') {
throw new BadRequestException('该合伙人合作已暂停,请联系客服人员');
}
}
private async assertPartnerAccountByPhone(phone: string) {
const account = await this.prisma.partnerAccount.findUnique({
where: { phone },
});
if (!account) throw new BadRequestException('未找到合伙人账号');
if (account.status !== 'ACTIVE') throw new BadRequestException('该账号已暂停使用,请联系客服人员');
await this.assertPartnerAccountActive(account);
return account;
}
@@ -1061,7 +1083,7 @@ export class AuthService {
where: { phone: normalizedPhone },
});
if (!account) throw new BadRequestException('未找到合伙人账号');
if (account.status !== 'ACTIVE') throw new BadRequestException('该账号已暂停使用,请联系客服人员');
await this.assertPartnerAccountActive(account);
await this.syncTestFlagByPhone(normalizedPhone);
const primary = await this.resolvePrimaryAccount(account.id);
await this.prisma.partnerAccount.update({
@@ -1723,9 +1745,7 @@ export class AuthService {
throw new BadRequestException('首次登录请使用手机验证码,登录后将自动关联微信');
}
if (account.status !== 'ACTIVE') {
throw new BadRequestException('该账号已暂停使用,请联系客服人员');
}
await this.assertPartnerAccountActive(account);
account = await this.prisma.partnerAccount.update({
where: { id: account.id },
@@ -9,7 +9,7 @@
|----|------|
| A. mini-user 门店座机电话脱敏 | `maskPhone` 统一「中间四位隐藏」;门店详情已调用 |
| B. mini-user 套餐详情图 1/5 + 自动轮播 | `ProductCarousel` detail 变体 autoplay + 右下角 `1/5` 计数 |
| C. 合伙人账号暂停禁止登录 | `ACTIVE` → 登录/发码均拒,提示「该账号已暂停使用,请联系客服人员」 |
| C. 合伙人账号暂停禁止登录(双拦截) | 账号 `status=DISABLED` 或主账号 `bindingStatus=PAUSED` → 登录/发码/微信均拒,提示「该账号已暂停使用」或「该合伙人合作已暂停」 |
| D. 门店端 申请提现 / 筛选栏留白对齐 | `WithdrawPage` 左右内边距统一 `--space-page` |
| E. 门店端 门店休息中 → 是否开启营业 | 门店 PAUSED 时点击首页「扫码核销」即弹「是否开启营业?」,不进入扫码流程 |
| F. mini-user 门店套餐图片自适应完整显示 | `ProductCarousel` 新增 `imageFit="adaptive"`(widthFix + 动态高度),门店套餐详情不再裁剪 |
@@ -38,16 +38,29 @@
- 图片容器 `.store-package-detail-gallery` 预留右下角定位锚点(如需)。
- 无 API / schema 变更。
## C. 合伙人账号暂停禁止登录
## C. 合伙人账号暂停禁止登录(双拦截:账号停用 + 合伙人绑定暂停)
- 后端 `POST /partner/auth/login/sms``server/.../modules/iam/auth.service.ts``loginPartner` ~L1063、`assertPartnerAccountByPhone` ~L307):账号 `status !== 'ACTIVE'` 时当前抛「合伙人账号已停用」。
- 改为抛出:`该账号已暂停使用,请联系客服人员`
- 同时覆盖发码预检 `assertSmsSendAllowed`PARTNER_LOGIN 场景 ~L341)与 `checkPartnerPhone`,使「暂停」账号无法获取短信验证码。
> 两个状态字段、两个枚举,**不要混**:
> | 维度 | 字段 | 枚举 | 取值 |
> |------|------|------|------|
> | 登录账号启用状态 | `partner_account.status` | `AccountStatus` | `ACTIVE` / `DISABLED` |
> | 城市合伙人绑定状态 | `partner_account.bindingStatus` | `CityPartnerStatus` | `ACTIVE` / `PAUSED` |
> 列表(`admin/partners`)里能看到的是 `bindingStatus`;主账号自身 `status` 不在列表返回体(仅子账号 `children[].status` 有)。
- 拦截规则(**主账号、子账号都拦**):
1. 登录账号自身 `status !== 'ACTIVE'``DISABLED`,主账号 / 子账号均适用)→ 抛 `该账号已暂停使用,请联系客服人员`
2. 所属**主账号** `bindingStatus !== 'ACTIVE'``PAUSED`,城市合伙人绑定暂停)→ 抛 `该合伙人合作已暂停,请联系客服人员`
- 子账号的绑定状态以其**父主账号**为准(`resolvePrimaryAccount`)。
- 后端统一闸门:新增 `assertPartnerAccountActive(account)``auth.service.ts` ~L307 后),一次性校验以上两条;由以下入口复用:
- `assertPartnerAccountByPhone`~L320):被 `checkPartnerPhone``POST /partner/auth/phone/check`)、发码预检 `assertSmsSendAllowed`PARTNER_LOGIN / PARTNER_PROXY_ORDER 场景)调用 → 暂停账号在「手机号校验」阶段即被拦截,无法获取短信验证码。
- `loginPartner``POST /partner/auth/login/sms`~L1063 后)。
- `loginPartnerWechat``POST /partner/auth/login/wechat`~L1726 后)。
- 前端 `apps/h5-partner/src/pages/LoginPage.tsx`
- `formatPartnerError`(~L69):命中「已暂停 / 已停用」分支时返回 `该账号已暂停使用,请联系客服人员`
- `formatWechatError`~L80):同上,覆盖微信登录路径
- 枚举现状:`AccountStatus``ACTIVE` / `DISABLED``schema.prisma` ~L244)。本次**复用 `DISABLED` 即视为暂停**,不新增枚举,避免迁移;若后续需区分「暂停(可恢复)」与「停用(永久)」,再评估新增 `SUSPENDED`
- 统一文案`该账号已暂停使用,请联系客服人员`
- `formatPartnerError`~L69/ `formatWechatError`~L84:命中「已暂停 / 已停用」分支映射 `该账号已暂停使用,请联系客服人员`;其余未匹配错误(含 `该合伙人合作已暂停…`)原样透出 `return text`
- 枚举现状:`AccountStatus``schema.prisma` ~L244)与 `CityPartnerStatus`(~L203)均为既有,本次**无任何 Prisma 迁移**
- 统一文案:
- 账号停用`该账号已暂停使用,请联系客服人员`
- 合伙人绑定暂停:`该合伙人合作已暂停,请联系客服人员`
## D. 门店端 申请提现 / 筛选栏左右留白对齐
@@ -102,7 +115,9 @@
- [ ] 门店详情座机显示形如 `0379-12****78`(中间四位隐藏),手机号仍 `138****8000`;拨打为明文。
- [ ] 套餐详情图多张时右下角显示 `1/5` 分页计数,并自动轮播(约 3.5s 切换);单图不计数、不轮播;点击仍可预览。
- [ ] 合伙人账号处于非 ACTIVE(暂停 / 停用)时:短信登录与微信登录均被拦截,提示「该账号已暂停使用,请联系客服人员」,无法进入。
- [ ] 合伙人**账号** `status=DISABLED`(主账号或子账号)时:短信登录与微信登录均被拦截,提示「该账号已暂停使用,请联系客服人员」,无法进入。
- [ ] 合伙人**绑定** `bindingStatus=PAUSED`(主账号)时:无论用主账号还是其任一子账号登录,均被拦截,提示「该合伙人合作已暂停,请联系客服人员」。
- [ ] 以上拦截在「手机号校验(`phone/check`)」阶段即生效,暂停账号拿不到短信验证码。
- [ ] 门店管理(结算提现)页:申请提现按钮与筛选栏左右留白与其他区块一致(统一 `--space-page`),不再贴边。
- [ ] 门店休息中(PAUSED)时:点击门店端首页「扫码核销」按钮**立即**弹「门店目前休息中无法核销,是否开启营业?」,**不进入扫码流程**;确认开启后门店状态刷新、可再次扫码;取消则维持拦截;未过审门店开张被拒时给出对应提示。
- [ ] 门店套餐详情图片按真实比例完整显示、不再被裁切(轮播高度随图自适应);自动轮播与 `1/5` 计数仍正常。