# 商家门店分管账号 Implementation Plan

**功能标识**：`022-merchant-store-subaccounts` | **日期**：2026-08-28 | **规格**：[spec.md](spec.md)

**目标**：为商家主账号提供可分配多个店铺的独立分管账号，使其按授权门店操作订单、商品和退款，并将店铺通知发送给所有可接收分管账号，同时保持平台商家管理只统计主账号。

**架构**：分管账号复用 `info_user` 认证主键并使用独立 `user_type=5`，通过 `merchant_subaccount_store` 表表达多店授权。后端用集中式门店访问服务校验真实数据归属，通知通过门店接收者路由服务选择在线分管账号或主账号兜底。平台以主商家下级视图呈现分管账号，商家端新增账号管理页。

**技术栈**：Java 21、Spring Boot、MyBatis/MyBatis-Plus、MySQL、Redis、JUnit 5、Mockito、Vue 2、Element UI、vue-i18n。

## 全局约束

- 模块依赖保持 `ruoyi-admin -> ruoyi-system`，系统模块不得导入 `com.ruoyi.app.*`。
- App Controller 使用明确 DTO、`@RequestBody`、`@RequestHeader String token` 和显式查询参数，不使用 `Map` 入参。
- 数据库变更只追加到 `updatesql/sql.md`，不直接执行。
- 商家端和平台端新增文案同步维护简中、繁中、英文、越南语，并保持原 CRLF。
- 保留当前工作区已有订单状态及推送调整，不覆盖无关脏文件。
- 密码、Token、CID 和支付资料不得写入日志或响应。

## 技术上下文

**存储**：`info_user` 增加所属主账号、主账号控制状态和最后登录时间；新增账号门店关联表。
**认证**：沿用商家端 JWT 与 Redis 会话，类型 5 使用商家 App/PC token 前缀。
**授权**：每次请求按数据库当前状态与目标实体真实 `mdId` 校验，不把店铺权限缓存进 Token。
**推送**：沿用 iOS `msdstore` 和 `PushEvent -> push_message`，不新增 Android FCM。
**兼容性**：类型 1、3、4 原有业务保持；无分管账号时通知继续发主账号。

## Constitution Check

- [通过] 新功能采用独立规格、计划、数据模型、接口契约和后续任务清单。
- [通过] 权限校验集中到服务层，Controller 仍执行显式身份和参数边界。
- [通过] 不引入反向模块依赖，不创建第二套认证系统。
- [通过] 支付与退款只增加授权前置校验，不改变渠道状态机。
- [通过] SQL 仅形成迁移文档，四语种与前端换行要求进入交付检查。

## 项目结构与职责

### 后端新增

```text
ruoyi-system/src/main/java/com/ruoyi/system/domain/MerchantSubaccountStore.java
ruoyi-system/src/main/java/com/ruoyi/system/domain/constants/MerchantAccountConstants.java
ruoyi-system/src/main/java/com/ruoyi/system/mapper/MerchantSubaccountStoreMapper.java
ruoyi-system/src/main/resources/mapper/infouser/MerchantSubaccountStoreMapper.xml
ruoyi-system/src/main/java/com/ruoyi/system/service/IMerchantSubaccountStoreService.java
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/MerchantSubaccountStoreServiceImpl.java
ruoyi-system/src/main/java/com/ruoyi/system/service/MerchantStoreAccessService.java

ruoyi-admin/src/main/java/com/ruoyi/app/user/MerchantSubaccountController.java
ruoyi-admin/src/main/java/com/ruoyi/app/user/MerchantSubaccountAdminController.java
ruoyi-admin/src/main/java/com/ruoyi/app/user/MerchantSubaccountApplicationService.java
ruoyi-admin/src/main/java/com/ruoyi/app/user/dto/MerchantSubaccountCreateRequest.java
ruoyi-admin/src/main/java/com/ruoyi/app/user/dto/MerchantSubaccountUpdateRequest.java
ruoyi-admin/src/main/java/com/ruoyi/app/user/dto/MerchantSubaccountPasswordRequest.java
ruoyi-admin/src/main/java/com/ruoyi/app/user/dto/MerchantSubaccountStatusRequest.java
ruoyi-admin/src/main/java/com/ruoyi/app/user/dto/MerchantSubaccountView.java
ruoyi-admin/src/main/java/com/ruoyi/app/order/MerchantNotificationRouter.java
```

`MerchantStoreAccessService` 只处理账号、店铺和实体归属授权；`MerchantSubaccountApplicationService` 负责创建、状态、密码、授权事务及 Redis 会话撤销；`MerchantNotificationRouter` 留在 admin 模块以使用 `PayPush` 和 `PushEventService`。

### 后端修改

```text
ruoyi-system/src/main/java/com/ruoyi/system/domain/InfoUser.java
ruoyi-system/src/main/resources/mapper/infouser/InfoUserMapper.xml
ruoyi-system/src/main/java/com/ruoyi/system/mapper/InfoUserMapper.java
ruoyi-admin/src/main/java/com/ruoyi/app/user/InfoUserController.java
ruoyi-admin/src/main/java/com/ruoyi/app/mendian/PosStoreController.java
ruoyi-admin/src/main/java/com/ruoyi/app/mendian/PosFoodController.java
ruoyi-admin/src/main/java/com/ruoyi/app/mendian/PosFenleiController.java
ruoyi-admin/src/main/java/com/ruoyi/app/mendian/FoodSpecController.java
ruoyi-admin/src/main/java/com/ruoyi/app/order/PosOrderShOprateController.java
ruoyi-admin/src/main/java/com/ruoyi/app/order/PosOrderController.java
ruoyi-admin/src/main/java/com/ruoyi/app/order/UserOrderController.java
ruoyi-admin/src/main/java/com/ruoyi/app/order/PosOrderQsOprateController.java
ruoyi-admin/src/main/java/com/ruoyi/app/order/DeliveryOrderNotificationService.java
ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties
ruoyi-admin/src/main/resources/i18n/messages_zh_TW.properties
ruoyi-admin/src/main/resources/i18n/messages_en_US.properties
ruoyi-admin/src/main/resources/i18n/messages_vi.properties
updatesql/sql.md
```

### 商家端

```text
E:/QtwCode/foodie/foodie-store/src/api/subaccount.js
E:/QtwCode/foodie/foodie-store/src/views/MerchantSubaccount.vue
E:/QtwCode/foodie/foodie-store/src/router/index.js
E:/QtwCode/foodie/foodie-store/src/components/Aside.vue
E:/QtwCode/foodie/foodie-store/src/components/Header.vue
E:/QtwCode/foodie/foodie-store/src/views/AcidrollingCapacity.vue
E:/QtwCode/foodie/foodie-store/src/api/store.js
E:/QtwCode/foodie/foodie-store/src/api/food.js
E:/QtwCode/foodie/foodie-store/src/lang/zh.js
E:/QtwCode/foodie/foodie-store/src/lang/tw.js
E:/QtwCode/foodie/foodie-store/src/lang/en.js
E:/QtwCode/foodie/foodie-store/src/lang/vi.js
```

主账号显示分管账号菜单；类型 5 只显示订单、授权店铺和商品相关入口。店铺页面对类型 5 隐藏新增和删除，但后端仍强制校验。现有缺少 token 的店铺/商品请求补齐 `isToken: true`。

### 平台端

```text
E:/QtwCode/foodie/foodie-admin-vue/src/views/infouser/user/sjuser.vue
E:/QtwCode/foodie/foodie-admin-vue/src/api/infouser/user.js
E:/QtwCode/foodie/foodie-admin-vue/src/api/language/language.zh_CN.js
E:/QtwCode/foodie/foodie-admin-vue/src/api/language/language.zh_TW.js
E:/QtwCode/foodie/foodie-admin-vue/src/api/language/language.en_US.js
E:/QtwCode/foodie/foodie-admin-vue/src/api/language/language.vi.js
```

平台商家行新增“分管账号”按钮和对话框，不新增顶级菜单；只提供查看和平台启停。

## 实施阶段

### Phase 1：账号模型与授权基础

1. 为 `InfoUser` 补齐三个字段和 MyBatis 映射，建立类型 5 常量，SQL 追加迁移语句。
2. 建立关联实体、Mapper 和 Service，覆盖账号—店铺唯一关系与按店铺/账号查询。
3. 实现 `MerchantStoreAccessService` 的 `requireOwner`、`getAccessibleStoreIds`、`requireStoreAccess`、`requireOrderAccess` 和 `requireFoodAccess`。
4. 测试主账号全店权限、分管账号多店权限、跨商家授权拒绝、停用状态和授权撤销立即生效。

### Phase 2：分管账号生命周期与登录

1. 实现商家端创建、列表、编辑、重置密码和主账号启停接口，所有写入使用事务。
2. 实现平台下级列表和平台启停接口，严格分离两个状态字段。
3. 扩展 `shanglodeing` 支持类型 5，签发 Token 前检查自身与主账号状态，更新最后登录时间。
4. 增加商家退出接口；停用或重置密码时删除 App/PC 会话。
5. 回归平台 `userType=1` 列表、导出和统计，证明类型 5 不出现、不计数、不进入审核。
6. 扩展 `@Auth` 支持显式启用 Redis 会话认证；商家业务接口在认证切面统一完成 JWT 与 JTI 校验，Controller 不再调用会话服务二次认证。

### Phase 3：店铺、商品和订单数据权限

1. `getmystorelist` 对主账号按所有权查询，对分管账号按授权集合查询。
2. `addmendian` 增加 token 并强制主账号；店铺详情、修改、营业状态和营业时间校验实际店铺。
3. 商品、分类和规格的商家端查询与写入补齐 token；修改和删除先读取目标实体的实际 `mdId` 再授权。
4. 商家订单列表按可访问 `mdId` 集合查询；详情、接单后操作、取消和退款在状态流转前执行订单门店授权。
5. 对收款配置、提现、结算、跨店财务及分管账号管理入口执行主账号校验；类型 5 默认拒绝未明确开放的商家能力。

### Phase 4：通知路由

1. 实现门店通知接收者查询：有效授权、双重启用、主账号正常、App 会话有效、CID 非空。
2. 每个账号分别发布 `PushEvent`；CID 去重外部推送，单账号异常隔离。
3. 替换当前有效商家推送调用点：支付成功/订单开放、骑手接单、订单取消等均传入 `mdId`。
4. 无分管账号、无人具有 App 推送资格或门店异常时按规格向主账号兜底。

### Phase 5：两个前端

1. 商家端新增分管账号管理页，支持多店选择、创建、编辑、密码重置和主账号启停。
2. 根据类型 1/5 调整菜单和店铺页面按钮，分管账号不显示新增/删除店铺及敏感模块。
3. 平台商家列表新增下属账号对话框，显示账号、状态、在线/最近登录和负责店铺，只开放平台启停。
4. 两端补齐四语种文案并保持 CRLF。

### Phase 6：验证与交付

1. 运行定向权限、登录、平台隔离、订单操作和通知路由测试。
2. 使用 JDK 21 构建 `ruoyi-admin` 及依赖模块。
3. 构建 `foodie-store` 与 `foodie-admin-vue`。
4. 按 `quickstart.md` 执行越权、双重停用、通知全发和主账号兜底场景。
5. 检查三个仓库的暂存范围、换行和差异；SQL 只保留在迁移文档。
6. 增加认证切面定向测试，覆盖有效会话放行、Redis 会话缺失拒绝、未启用会话选项时保持现有非商家接口行为，并回归商家店铺及订单入口。

## 测试文件规划

```text
ruoyi-system/src/test/java/com/ruoyi/system/service/MerchantStoreAccessServiceTest.java
ruoyi-system/src/test/java/com/ruoyi/system/mapper/MerchantSubaccountStoreMapperXmlTest.java
ruoyi-admin/src/test/java/com/ruoyi/app/user/MerchantSubaccountControllerTest.java
ruoyi-admin/src/test/java/com/ruoyi/app/user/MerchantSubaccountAdminControllerTest.java
ruoyi-admin/src/test/java/com/ruoyi/app/order/MerchantNotificationRouterTest.java
ruoyi-admin/src/test/java/com/ruoyi/app/mendian/PosFoodControllerAccessTest.java
```

并扩展现有：

```text
ruoyi-admin/src/test/java/com/ruoyi/app/user/InfoUserControllerTest.java
ruoyi-admin/src/test/java/com/ruoyi/app/mendian/PosStoreControllerTest.java
ruoyi-admin/src/test/java/com/ruoyi/app/order/PosOrderShOprateControllerTest.java
ruoyi-admin/src/test/java/com/ruoyi/app/order/DeliveryOrderNotificationServiceTest.java
```

## 复杂度说明

| 复杂度 | 必要性 | 未采用的简单方案 |
|---|---|---|
| `info_user` 类型 5 + 关联表 | 同时复用认证并隔离平台商家统计，支持账号与店铺多对多 | 类型 1 加角色容易污染所有商家查询；单独账号表复制认证体系 |
| 双重状态 | 平台强制停用不能被主账号覆盖 | 共用一个状态无法区分操作者和恢复权限 |
| 集中权限服务 | 多个旧 Controller 的归属规则不同，必须统一防止 IDOR | 仅前端隐藏或各 Controller 手写条件容易漏改 |
| 通知路由服务 | 同店多接收者、消息逐账号归属、主账号兜底需要统一行为 | 在每个订单入口复制循环会产生规则漂移 |
| `@Auth` 可选会话认证 | 在不改变骑手、普通用户认证行为的前提下，把商家 JWT 与 Redis 会话校验集中到 Controller 之前 | 全局强制 Redis 校验会扩大兼容性影响；Controller 手工调用会产生重复认证和遗漏风险 |
