# 商家门店分管账号 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 手工调用会产生重复认证和遗漏风险 |