5b910ad)e63cb3a)7862f9b)7862f9b)7862f9b、5ae3c22)dc595b9)4403def)33005c6)foodie-store 提交 4d4297d)foodie-admin-vue 提交 27b3d6a)执行要求: 按任务顺序实施;每个后端任务遵循测试先行,先确认定向测试失败,再写最小实现并复测。提交标题和正文使用中文。
输入:spec.md、plan.md、research.md、data-model.md、API 契约
C:\Users\qmj\.jdks\graalvm-jdk-21.0.7。ruoyi-admin -> ruoyi-system。@RequestBody、@RequestHeader 和显式查询参数,不使用 Map。updatesql/sql.md,不直接执行。.claude/homunculus/observations.jsonl。文件:
ruoyi-system/src/main/java/com/ruoyi/system/domain/InfoUser.javaruoyi-system/src/main/java/com/ruoyi/system/domain/constants/MerchantAccountConstants.javaruoyi-system/src/main/resources/mapper/infouser/InfoUserMapper.xmlruoyi-system/src/main/java/com/ruoyi/system/domain/MerchantSubaccountStore.javaruoyi-system/src/main/java/com/ruoyi/system/mapper/MerchantSubaccountStoreMapper.javaruoyi-system/src/main/resources/mapper/infouser/MerchantSubaccountStoreMapper.xmlruoyi-system/src/main/java/com/ruoyi/system/service/IMerchantSubaccountStoreService.javaruoyi-system/src/main/java/com/ruoyi/system/service/impl/MerchantSubaccountStoreServiceImpl.javaruoyi-system/src/test/java/com/ruoyi/system/mapper/MerchantSubaccountStoreMapperXmlTest.javaupdatesql/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);
merchant_owner_id、subaccount_status、last_login_at 映射存在,并断言关联表查询使用参数绑定和唯一字段。mvn -pl ruoyi-system -Dtest=MerchantSubaccountStoreMapperXmlTest test,确认因实体、Mapper 或 XML 尚不存在而失败。InfoUser 增加三个字段、访问器和 toString() 映射;在 MerchantAccountConstants 定义类型 1/5 和双重状态常量,禁止在多个 Controller 重复魔法字符串。updatesql/sql.md 追加 2026-08-28 迁移:三个 info_user 字段、所属主账号索引、关联表和两个索引,不执行 SQL。git diff --check -- ruoyi-system updatesql/sql.md。git commit -m "建立商家分管账号数据模型"。文件:
ruoyi-system/src/main/java/com/ruoyi/system/service/MerchantStoreAccessService.javaruoyi-system/src/test/java/com/ruoyi/system/service/MerchantStoreAccessServiceTest.javaruoyi-system/src/main/java/com/ruoyi/system/mapper/InfoUserMapper.javaruoyi-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);
mdId 与实体真实归属不一致时,以数据库实体的 mdId 为准拒绝越权。mvn -pl ruoyi-system -Dtest=MerchantStoreAccessServiceTest test,确认服务尚不存在而失败。ServiceException 返回国际化键;每次调用重新读取当前账号和授权,不把 storeIds 缓存进 Token。git commit -m "集中商家门店数据权限校验"。文件:
ruoyi-admin/src/main/java/com/ruoyi/app/user/MerchantSubaccountController.javaruoyi-admin/src/main/java/com/ruoyi/app/user/MerchantSubaccountApplicationService.javaruoyi-admin/src/main/java/com/ruoyi/app/user/dto/MerchantSubaccountCreateRequest.javaruoyi-admin/src/main/java/com/ruoyi/app/user/dto/MerchantSubaccountUpdateRequest.javaruoyi-admin/src/main/java/com/ruoyi/app/user/dto/MerchantSubaccountPasswordRequest.javaruoyi-admin/src/main/java/com/ruoyi/app/user/dto/MerchantSubaccountStatusRequest.javaruoyi-admin/src/main/java/com/ruoyi/app/user/dto/MerchantSubaccountView.javaruoyi-admin/src/test/java/com/ruoyi/app/user/MerchantSubaccountControllerTest.javaruoyi-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
subaccountStatus,不能修改平台 status;密码重置和停用必须撤销 App/PC Redis 会话。mvn -pl ruoyi-admin -am -Dtest=MerchantSubaccountControllerTest -Dsurefire.failIfNoSpecifiedTests=false test,确认 RED。user_name 与 phone,固定 user_type=5、audit_status=1、status=0、subaccount_status=0,不创建钱包和商户资质。git commit -m "新增商家分管账号管理接口"。文件:
ruoyi-admin/src/main/java/com/ruoyi/app/user/MerchantSubaccountAdminController.javaruoyi-admin/src/test/java/com/ruoyi/app/user/MerchantSubaccountAdminControllerTest.javaruoyi-admin/src/test/java/com/ruoyi/app/user/InfoUserControllerTest.javaruoyi-admin/src/main/java/com/ruoyi/app/user/InfoUserController.java接口:
GET /infouser/merchant-subaccounts?merchantUserId={id}
PUT /infouser/merchant-subaccounts/{subaccountUserId}/platform-status
status,停用撤销会话;平台恢复不能覆盖 subaccountStatus=1。userType=1 结果和总数不包含分管账号;平台通用新增/编辑不能把账号类型写成 5。@PreAuthorize 列表/编辑权限和类型保护。git commit -m "隔离平台商家与分管账号管理"。文件:
ruoyi-admin/src/main/java/com/ruoyi/app/user/InfoUserController.javaruoyi-admin/src/test/java/com/ruoyi/app/user/InfoUserControllerTest.java行为:
// shanglodeing 在签发 token 前必须满足
user.getStatus().equals("0");
// 类型 5 还必须满足 subaccountStatus=0 且 owner.status=0
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 "完善商家账号登录与停用校验"。文件:
ruoyi-admin/src/main/java/com/ruoyi/app/mendian/PosStoreController.javaruoyi-admin/src/main/java/com/ruoyi/app/mendian/PosFoodController.javaruoyi-admin/src/main/java/com/ruoyi/app/mendian/PosFenleiController.javaruoyi-admin/src/main/java/com/ruoyi/app/mendian/FoodSpecController.javaruoyi-admin/src/test/java/com/ruoyi/app/mendian/PosStoreControllerTest.javaruoyi-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
userId、mdId、商品 ID、分类 ID 或规格 ID 不能访问未授权店铺。PosStoreControllerTest,PosFoodControllerAccessTest 确认 RED。@RequestHeader String token 和明确 DTO/参数;授权使用目标实体真实门店,忽略请求体中的所有者字段。addmendian 调用 requireOwner 并由后端写入当前主账号 ID;分管账号前端隐藏入口不能替代该校验。git commit -m "限制店铺商品操作到授权门店"。文件:
ruoyi-admin/src/main/java/com/ruoyi/app/order/PosOrderShOprateController.javaruoyi-admin/src/main/java/com/ruoyi/app/order/PosOrderController.javaruoyi-admin/src/test/java/com/ruoyi/app/order/PosOrderShOprateControllerTest.java必须保护的能力:
orderList、getstoreorderlist、订单详情、商家确认/出餐/取消、现金确认、发票查看/作废、现有退款入口
md_id IN (...) 查询;同主商家的未授权店铺订单不返回。shId 属于主账号但 mdId 未授权时仍拒绝;授权门店订单继续执行现有骑手先接单、支付成功后开放和状态流转规则。requireOrderAccess,再进入现有渠道退款校验;越权请求不得创建退款记录或改变订单状态。PosOrderShOprateControllerTest 确认 RED。userId=shId 的判断;主账号使用 owner ID,分管账号使用授权门店集合。git commit -m "限制订单退款操作到授权门店"。文件:
ruoyi-admin/src/main/java/com/ruoyi/app/order/MerchantNotificationRouter.javaruoyi-admin/src/test/java/com/ruoyi/app/order/MerchantNotificationRouterTest.javaruoyi-admin/src/main/java/com/ruoyi/app/order/UserOrderController.javaruoyi-admin/src/main/java/com/ruoyi/app/order/PosOrderQsOprateController.javaruoyi-admin/src/main/java/com/ruoyi/app/order/PosOrderController.javaruoyi-admin/src/main/java/com/ruoyi/app/order/DeliveryOrderNotificationService.javaruoyi-admin/src/test/java/com/ruoyi/app/order/DeliveryOrderNotificationServiceTest.java产出接口:
void sendStoreNotification(Long storeId, Long ownerUserId,
String titleKey, String contentKey,
String body, String orderNo);
MerchantNotificationRouterTest,DeliveryOrderNotificationServiceTest 确认 RED。PushEvent 入库和 CID 去重;不要新增 Android FCM。git commit -m "按门店分管账号路由商家通知"。文件:
E:/QtwCode/foodie/foodie-store/src/api/subaccount.jsE:/QtwCode/foodie/foodie-store/src/views/MerchantSubaccount.vueE:/QtwCode/foodie/foodie-store/src/router/index.jsE:/QtwCode/foodie/foodie-store/src/components/Aside.vueE:/QtwCode/foodie/foodie-store/src/components/Header.vueE:/QtwCode/foodie/foodie-store/src/views/AcidrollingCapacity.vueE:/QtwCode/foodie/foodie-store/src/api/store.jsE:/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 "增加商家分管账号管理页面"。
文件:
E:/QtwCode/foodie/foodie-admin-vue/src/views/infouser/user/sjuser.vueE:/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 "在主商家下展示分管账号"。
文件:
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-store 和 foodie-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
| 需求 | 任务 |
|---|---|
| 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 |
文件:
ruoyi-system/src/main/java/com/ruoyi/system/utils/Auth.javaruoyi-system/src/main/java/com/ruoyi/system/utils/AuthAspect.javaruoyi-admin/src/main/java/com/ruoyi/app/user/MerchantTokenSessionService.javaMerchantTokenSessionService.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、暂存文件清单和测试结果,形成独立中文提交并推送当前分支。