tasks.md 22 KB

商家门店分管账号任务清单

执行状态

  • T001 数据模型与账号—店铺关联(提交 5b910ad
  • T002 集中式商家门店访问服务(提交 e63cb3a
  • T003 商家端分管账号管理接口(提交 7862f9b
  • T004 平台分管账号视图与商家列表隔离(提交 7862f9b
  • T005 商家登录、退出、双重状态和响应脱敏(提交 7862f9b5ae3c22
  • T006 店铺、商品、分类和规格权限收口(提交 dc595b9
  • T007 订单、发票、取消和退款权限收口(提交 4403def
  • T008 按真实门店路由商家通知(提交 33005c6
  • T009 商家端分管账号与角色界面(foodie-store 提交 4d4297d
  • T010 平台端主商家下属分管账号界面(foodie-admin-vue 提交 27b3d6a
  • T011 自动化测试、构建和规格回填
  • T011 数据库迁移后的联调、真机推送和完整手工验收(需人工环境)

执行要求: 按任务顺序实施;每个后端任务遵循测试先行,先确认定向测试失败,再写最小实现并复测。提交标题和正文使用中文。

输入spec.mdplan.mdresearch.mddata-model.mdAPI 契约

全局约束

  • JDK 使用 C:\Users\qmj\.jdks\graalvm-jdk-21.0.7
  • 模块依赖保持 ruoyi-admin -> ruoyi-system
  • Controller 请求使用明确 DTO、@RequestBody@RequestHeader 和显式查询参数,不使用 Map
  • 数据库变更只写入 updatesql/sql.md,不直接执行。
  • 两个 Vue 仓库新增文案同步维护简中、繁中、英文、越南语并保持 CRLF。
  • 保留工作区已有订单状态和推送调整;暂存前逐文件核对,禁止混入 .claude/homunculus/observations.jsonl
  • 密码、Token、CID 和支付资料不得出现在日志、错误响应或测试快照中。

Phase 1:数据模型与授权基础

T001 [US1] 建立分管账号字段和账号—店铺关联模型

文件:

  • 修改:ruoyi-system/src/main/java/com/ruoyi/system/domain/InfoUser.java
  • 创建:ruoyi-system/src/main/java/com/ruoyi/system/domain/constants/MerchantAccountConstants.java
  • 修改:ruoyi-system/src/main/resources/mapper/infouser/InfoUserMapper.xml
  • 创建:ruoyi-system/src/main/java/com/ruoyi/system/domain/MerchantSubaccountStore.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/test/java/com/ruoyi/system/mapper/MerchantSubaccountStoreMapperXmlTest.java
  • 修改:updatesql/sql.md

产出接口:

public static final String MerchantAccountConstants.SUBACCOUNT_USER_TYPE = "5";

Long InfoUser.getMerchantOwnerId();
String InfoUser.getSubaccountStatus();
Date InfoUser.getLastLoginAt();

List<Long> MerchantSubaccountStoreMapper.selectStoreIdsBySubaccountUserId(Long subaccountUserId);
List<InfoUser> MerchantSubaccountStoreMapper.selectEligibleSubaccountsByStoreId(Long storeId);
int MerchantSubaccountStoreMapper.deleteBySubaccountUserId(Long subaccountUserId);
int MerchantSubaccountStoreMapper.insertBatch(List<MerchantSubaccountStore> relations);
  • 写 XML 结构测试,断言 merchant_owner_idsubaccount_statuslast_login_at 映射存在,并断言关联表查询使用参数绑定和唯一字段。
  • 运行 mvn -pl ruoyi-system -Dtest=MerchantSubaccountStoreMapperXmlTest test,确认因实体、Mapper 或 XML 尚不存在而失败。
  • InfoUser 增加三个字段、访问器和 toString() 映射;在 MerchantAccountConstants 定义类型 1/5 和双重状态常量,禁止在多个 Controller 重复魔法字符串。
  • 实现关联实体、Mapper XML 和 Service;批量插入为空集合时直接返回,不生成非法 SQL。
  • updatesql/sql.md 追加 2026-08-28 迁移:三个 info_user 字段、所属主账号索引、关联表和两个索引,不执行 SQL。
  • 复跑定向测试并执行 git diff --check -- ruoyi-system updatesql/sql.md
  • 暂存本任务文件并提交:git commit -m "建立商家分管账号数据模型"

T002 [US2][US4] 实现集中式商家门店访问服务

文件:

  • 创建:ruoyi-system/src/main/java/com/ruoyi/system/service/MerchantStoreAccessService.java
  • 创建:ruoyi-system/src/test/java/com/ruoyi/system/service/MerchantStoreAccessServiceTest.java
  • 按需修改:ruoyi-system/src/main/java/com/ruoyi/system/mapper/InfoUserMapper.java
  • 按需修改:ruoyi-system/src/main/java/com/ruoyi/system/mapper/PosStoreMapper.java

产出接口:

public final class MerchantAccessContext {
    Long loginUserId();
    Long ownerUserId();
    boolean owner();
    Set<Long> storeIds();
}

MerchantAccessContext resolve(Long loginUserId);
void requireOwner(Long loginUserId);
Set<Long> getAccessibleStoreIds(Long loginUserId);
void requireStoreAccess(Long loginUserId, Long storeId);
PosOrder requireOrderAccess(Long loginUserId, Long orderId);
PosFood requireFoodAccess(Long loginUserId, Long foodId);
  • 写单元测试覆盖:类型 1 主账号获得全部自有店铺;类型 5 只获得授权店铺;类型 5 双重状态任一停用、所属主账号停用、跨商家关系、门店所有者变化均拒绝。
  • 增加订单和商品测试:请求参数中的 mdId 与实体真实归属不一致时,以数据库实体的 mdId 为准拒绝越权。
  • 运行 mvn -pl ruoyi-system -Dtest=MerchantStoreAccessServiceTest test,确认服务尚不存在而失败。
  • 实现服务并使用 ServiceException 返回国际化键;每次调用重新读取当前账号和授权,不把 storeIds 缓存进 Token。
  • 复跑测试,确认授权取消后无需重新登录即可生效。
  • 暂存本任务文件并提交:git commit -m "集中商家门店数据权限校验"

Phase 2:账号生命周期、平台隔离与登录

T003 [US1][US4] 实现商家端分管账号管理接口

文件:

  • 创建:ruoyi-admin/src/main/java/com/ruoyi/app/user/MerchantSubaccountController.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/test/java/com/ruoyi/app/user/MerchantSubaccountControllerTest.java
  • 修改:四个 ruoyi-admin/src/main/resources/i18n/messages_*.properties

接口:

GET  /merchant/subaccounts
POST /merchant/subaccounts
PUT  /merchant/subaccounts/{subaccountUserId}
PUT  /merchant/subaccounts/{subaccountUserId}/password
PUT  /merchant/subaccounts/{subaccountUserId}/status
  • 写 Controller/Service 测试,覆盖手机号全局重复、空姓名、空密码、空店铺、跨商家店铺、非主账号调用、修改他人分管账号、事务中任一授权失败整体回滚。
  • 写状态测试,断言主账号启停只修改 subaccountStatus,不能修改平台 status;密码重置和停用必须撤销 App/PC Redis 会话。
  • 运行 mvn -pl ruoyi-admin -am -Dtest=MerchantSubaccountControllerTest -Dsurefire.failIfNoSpecifiedTests=false test,确认 RED。
  • 实现 DTO 和接口;手机号规范化后同时写入 user_namephone,固定 user_type=5audit_status=1status=0subaccount_status=0,不创建钱包和商户资质。
  • 账号插入与授权批量写入使用同一事务;更新授权先验证完整店铺集合,再替换关系。
  • 四语种补齐账号重复、无权管理、店铺越权、平台停用等业务错误。
  • 复跑测试并提交:git commit -m "新增商家分管账号管理接口"

T004 [US5] 实现平台下属账号视图并隔离商家管理

文件:

  • 创建:ruoyi-admin/src/main/java/com/ruoyi/app/user/MerchantSubaccountAdminController.java
  • 创建:ruoyi-admin/src/test/java/com/ruoyi/app/user/MerchantSubaccountAdminControllerTest.java
  • 修改:ruoyi-admin/src/test/java/com/ruoyi/app/user/InfoUserControllerTest.java
  • 按需修改:ruoyi-admin/src/main/java/com/ruoyi/app/user/InfoUserController.java

接口:

GET /infouser/merchant-subaccounts?merchantUserId={id}
PUT /infouser/merchant-subaccounts/{subaccountUserId}/platform-status
  • 写平台接口测试:列表只接受类型 1 主账号;只返回其类型 5 子账号;响应不含密码、CID、Token 和商户资质。
  • 写平台状态测试:只修改 status,停用撤销会话;平台恢复不能覆盖 subaccountStatus=1
  • 扩展平台商家列表/导出服务测试,插入或模拟类型 5 后,userType=1 结果和总数不包含分管账号;平台通用新增/编辑不能把账号类型写成 5。
  • 运行两个定向测试类确认 RED,再实现 @PreAuthorize 列表/编辑权限和类型保护。
  • 复跑测试并提交:git commit -m "隔离平台商家与分管账号管理"

T005 [US1][US4][US5] 扩展商家登录、退出和会话失效

文件:

  • 修改:ruoyi-admin/src/main/java/com/ruoyi/app/user/InfoUserController.java
  • 修改:ruoyi-admin/src/test/java/com/ruoyi/app/user/InfoUserControllerTest.java

行为:

// shanglodeing 在签发 token 前必须满足
user.getStatus().equals("0");
// 类型 5 还必须满足 subaccountStatus=0 且 owner.status=0
  • 写测试覆盖:类型 5 正常登录;自身平台停用、主账号停用、主账号控制停用分别拒绝;类型 1 被平台停用也拒绝;失败分支不能返回成功响应或 Token。
  • 写成功测试,断言更新 CID 和 lastLoginAt,使用商家 App/PC token 前缀;退出只删除当前 JTI 会话。
  • 运行 InfoUserControllerTest 确认 RED。
  • shanglodeing 的类型集合扩展为 1、3、4、5,并在密码验证成功、Token 签发前调用账号可用性校验;修正当前捕获异常后落入成功响应的分支。
  • 新增 POST /infouser/user/merchantLogout,使用 @RequestHeader String token,验证并删除 JTI,不记录 token。
  • 复跑测试并提交:git commit -m "完善商家账号登录与停用校验"

Phase 3:店铺、商品、订单与退款权限

T006 [US2] 收口店铺和商品权限

文件:

  • 修改: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/test/java/com/ruoyi/app/mendian/PosStoreControllerTest.java
  • 创建:ruoyi-admin/src/test/java/com/ruoyi/app/mendian/PosFoodControllerAccessTest.java

必须保护的有效商家端能力:

店铺:getmystorelist、storelistlist、getstore、addmendian、delemendian、saveMdBusinessHours、营业状态/资料修改
商品:getidlist、getFoodPageList、setposfood、delefood、changerecommend
分类:getfenlei、addfenlei、delefenlei
规格:foodSpecPageList、saveFoodSpec、deleteFoodSpec、changeOpen、changeSpecValueOpen
  • 为主账号和类型 5 分别写列表测试:主账号返回全部自有店铺,分管账号返回所有授权店铺且不需要当前店铺。
  • 写越权测试:分管账号新增/删除店铺被拒;可编辑授权店铺;伪造 userIdmdId、商品 ID、分类 ID 或规格 ID 不能访问未授权店铺。
  • 运行 PosStoreControllerTest,PosFoodControllerAccessTest 确认 RED。
  • 为有效写接口补 @RequestHeader String token 和明确 DTO/参数;授权使用目标实体真实门店,忽略请求体中的所有者字段。
  • addmendian 调用 requireOwner 并由后端写入当前主账号 ID;分管账号前端隐藏入口不能替代该校验。
  • 复跑测试并提交:git commit -m "限制店铺商品操作到授权门店"

T007 [US2] 收口商家订单操作和退款权限

文件:

  • 修改: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/test/java/com/ruoyi/app/order/PosOrderShOprateControllerTest.java
  • 按实际有效退款调用修改对应支付 Controller/Service 的授权入口,不改变支付渠道状态机。

必须保护的能力:

orderList、getstoreorderlist、订单详情、商家确认/出餐/取消、现金确认、发票查看/作废、现有退款入口
  • 写列表测试:类型 5 按授权 md_id IN (...) 查询;同主商家的未授权店铺订单不返回。
  • 写操作测试:订单的 shId 属于主账号但 mdId 未授权时仍拒绝;授权门店订单继续执行现有骑手先接单、支付成功后开放和状态流转规则。
  • 写退款测试:先通过 requireOrderAccess,再进入现有渠道退款校验;越权请求不得创建退款记录或改变订单状态。
  • 运行 PosOrderShOprateControllerTest 确认 RED。
  • 用集中权限服务替换仅按当前 userId=shId 的判断;主账号使用 owner ID,分管账号使用授权门店集合。
  • 复跑订单定向测试及现有支付退款测试,提交:git commit -m "限制订单退款操作到授权门店"

Phase 4:通知路由

T008 [US3] 将商家通知路由到在线分管账号

文件:

  • 创建:ruoyi-admin/src/main/java/com/ruoyi/app/order/MerchantNotificationRouter.java
  • 创建:ruoyi-admin/src/test/java/com/ruoyi/app/order/MerchantNotificationRouterTest.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/PosOrderController.java
  • 修改:ruoyi-admin/src/main/java/com/ruoyi/app/order/DeliveryOrderNotificationService.java
  • 修改:ruoyi-admin/src/test/java/com/ruoyi/app/order/DeliveryOrderNotificationServiceTest.java

产出接口:

void sendStoreNotification(Long storeId, Long ownerUserId,
                           String titleKey, String contentKey,
                           String body, String orderNo);
  • 写测试覆盖:两个符合条件分管账号全部收到各自消息;主账号不重复;无人具有 App 会话/CID 时主账号兜底;平台或主账号停用的分管账号排除;相同 CID 外部推送去重。
  • 写失败隔离测试:一个接收者推送抛异常,其他账号仍写消息并尝试推送;门店为空时只向 owner 兜底。
  • 运行 MerchantNotificationRouterTest,DeliveryOrderNotificationServiceTest 确认 RED。
  • 实现接收者解析、Redis App 会话检查、逐账号 PushEvent 入库和 CID 去重;不要新增 Android FCM。
  • 将支付成功/订单开放、骑手接单、订单取消等当前有效商家推送调用替换为路由服务,并保留现有订单状态调整。
  • 复跑通知和订单生命周期测试,提交:git commit -m "按门店分管账号路由商家通知"

Phase 5:商家端与平台端

T009 [US1][US2] 增加商家端分管账号管理与角色界面

文件:

  • 创建: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/*.js

  • [ ] 新增 API 封装,所有分管账号、店铺和商品写请求设置 isToken: true;密码沿用现有 RSA 加密工具后提交。

  • [ ] 新增管理页:列表、创建、编辑、多店选择、主账号启停和密码重置;平台停用时展示状态且主账号启用按钮不能宣称已恢复。

  • [ ] 仅类型 1 显示“分管账号”菜单;类型 5 显示订单、授权店铺和商品入口,隐藏新增/删除店铺、分管账号、账单、支付配置及其他敏感模块。

  • [ ] 店铺列表类型 5 不显示新增/删除按钮,但允许编辑授权店铺;退出登录先调用后端退出接口,再清理本地 token。

  • [ ] 在 zh.js/tw.js/en.js/vi.js 同一嵌套对象增加语义化英文驼峰 key,不新增 text1 类编号。

  • [ ] 使用 git diff --check 和换行检查确认相关 Vue/JS 仍为 CRLF,运行 npm run build

  • [ ] 在 foodie-store 仓库提交:git commit -m "增加商家分管账号管理页面"

T010 [US5] 在平台主商家下呈现分管账号

文件:

  • 修改: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.*.js

  • [ ] 在商家行增加“分管账号”按钮,打开抽屉或对话框并传当前类型 1 主账号 ID,不新增顶级商家行或商家审核入口。

  • [ ] 展示姓名、手机号、双重状态、在线/最近登录、负责店铺和创建时间;不展示密码、CID 或资质字段。

  • [ ] 平台只提供强制停用/恢复按钮;不提供新增、密码重置和店铺授权编辑。

  • [ ] 四语种补齐文案并保持 CRLF,运行 npm run build:prod

  • [ ] 在 foodie-admin-vue 仓库提交:git commit -m "在主商家下展示分管账号"


Phase 6:集成验证与交付

T011 全量验证、规格回填与推送

文件:

  • 修改:specs/022-merchant-store-subaccounts/tasks.md(完成状态)
  • 按实际结果修改:specs/022-merchant-store-subaccounts/quickstart.md

  • [ ] 使用 JDK 21 运行 quickstart.md 中全部定向测试,记录测试数和结果。

  • [ ] 运行 mvn -pl ruoyi-admin -am -DskipTests package,再运行项目约定完整回归;如有失败,区分本功能回归与既有失败并记录证据。

  • [ ] 构建 foodie-storefoodie-admin-vue,确认四语言文件解析成功。

  • [ ] 执行手工验收:多店授权、越权拒绝、授权即时撤销、双重停用、主商家停用、通知全发、CID 去重、主账号兜底、平台商家数量不变。

  • [ ] 检查三个仓库 git diff --check、换行、编码和暂存清单;后端不得混入 .claude/homunculus/observations.jsonl 或其他原有脏文件。

  • [ ] 更新任务勾选和验证结果,后端规格/SQL/源码/测试形成中文提交;分别推送三个仓库当前分支。

依赖关系

T001 -> T002 -> T003 -> T004 -> T005
                  |       |
                  +------>T006 -> T007 -> T008
                          |             |
                          +-----------> T009
                                        |
T004 --------------------------------> T010
T001..T010 --------------------------> T011
T005..T007 --------------------------> T012
  • T001、T002 是所有业务故事的阻塞基础。
  • T003 完成后可开发商家端页面;T004 完成后可开发平台页面。
  • T006、T007 必须先于通知路由最终接入,确保通知目标与实际门店权限一致。
  • T009 和 T010 位于不同仓库,可独立构建,但不得在未完成后端契约前联调。

需求覆盖

需求 任务
FR-001、FR-002、FR-003、FR-004、FR-005、FR-006、FR-007、FR-008 T001、T003、T005
FR-009、FR-010、FR-011、FR-012、FR-013、FR-014、FR-015、FR-016 T002、T006、T007、T009
FR-017、FR-018、FR-019、FR-020、FR-021、FR-022、FR-023 T008
FR-024、FR-025、FR-026、FR-027 T007、T009、T011
FR-028、FR-029、FR-030、FR-031、FR-032、FR-033、FR-034、FR-035 T004、T005、T010
SC-001、SC-002、SC-003、SC-004、SC-005、SC-006、SC-007、SC-008、SC-009 T003~T011
FR-036、FR-037、SC-010 T012

Phase 7:统一商家会话认证

T012 将商家 Redis 会话校验收口到认证层

文件:

  • 修改:ruoyi-system/src/main/java/com/ruoyi/system/utils/Auth.java
  • 修改:ruoyi-system/src/main/java/com/ruoyi/system/utils/AuthAspect.java
  • 修改:ruoyi-admin/src/main/java/com/ruoyi/app/user/MerchantTokenSessionService.java
  • 修改:当前调用 MerchantTokenSessionService.requireUserId 的商家 Controller
  • 新增或修改:认证切面、商家店铺和商家订单定向测试

  • [x] 先写认证切面测试,覆盖启用会话校验时有效 JTI 放行、缺失 JTI/Redis Key 拒绝,以及默认 @Auth 不增加 Redis 会话要求;运行并确认 RED。

  • [x] 为 @Auth 增加默认关闭的会话校验选项,由 AuthAspect 在 JWT 校验通过后检查 JTI 对应 Redis Key,并使用现有国际化登录失效错误。

  • [x] 商家业务入口显式启用会话校验,删除 Controller 对 MerchantTokenSessionService.requireUserId 的调用;Controller 只从已验证 Token 获取登录用户 ID 并继续执行目标实体归属校验。

  • [x] MerchantTokenSessionService 删除身份认证职责,只保留退出当前会话、撤销账号全部 App/PC 会话和在线状态查询。

  • [x] 运行认证切面、商家登录、店铺和订单定向测试,并使用 JDK 21 构建 ruoyi-admin 及依赖模块。

  • [x] 检查 git diff --check、暂存文件清单和测试结果,形成独立中文提交并推送当前分支。