# Task 6 独立核验报告:指定商品会员价机制(BASIC 纳入) > **核验方:** 独立核验智能体 > **核验时间:** 2026-10-24 > **事实源:** 代码实际行为(不信任 exec-task6.md 结论) --- ## 一、核验范围 | # | 核验项 | 说明 | |---|--------|------| | 1 | `getEligibleMemberPrice` 逻辑 | min_discount_level=NULL 时按 BASIC 判定;等级比较方向正确;FREE/PROVIDER 不误享 | | 2 | 调用点全覆盖 | 单商品下单、多商品下单;确认无绕过路径;评估 CartService/ProductDTO 行为 | | 3 | `ActivityDTO.resolveMemberPrice` | BASIC 纳入;FREE/PROVIDER 不享 | | 4 | 限时特价/秒杀路径 | 不受本次改动影响 | | 5 | 编译验证 | `mvn clean compile -q` 退出码=0 | --- ## 二、逐条核验 ### 核验项 1:`ProductOrderService.getEligibleMemberPrice` **代码位置:** `ProductOrderService.java` 第 2166–2188 行 ```java private Integer getEligibleMemberPrice(Product product, Long buyerId) { if (product == null || product.getMemberPrice() == null || product.getMemberPrice() <= 0) { return product != null ? product.getPrice() : 0; } // 用户会员等级(FREE 兜底) String userLevel = "FREE"; if (buyerId != null) { User user = userMapper.selectById(buyerId); if (user != null) { userLevel = membershipService.getMemberLevel(buyerId); // ← 行 2175 } } // 产品未设置最低级别时默认 BASIC String minLevel = (product.getMinDiscountLevel() == null || product.getMinDiscountLevel().isEmpty()) ? "BASIC" : product.getMinDiscountLevel(); // ← 行 2179-2180 // 比较级别数字:级别数字越小越低 int userLevelOrder = membershipService.getLevelOrder(userLevel); // ← 行 2182 int minLevelOrder = membershipService.getLevelOrder(minLevel); // ← 行 2183 // 用户级别 >= 最低要求级别,则可享会员价 if (userLevelOrder >= minLevelOrder) { // ← 行 2185 return product.getMemberPrice(); } return product.getPrice(); } ``` **`membershipService.getMemberLevel` 行为**(MembershipService.java 行 1261–1283): - `buyerId` 为 null → 返回 "FREE" - `user.getMemberLevel() == "PROVIDER"` → 强制转为 "FREE"(行 1270-1271) - 家庭 + 个人取更高级别(`resolveEffectiveLevel`,行 1309-1323) **production `getLevelOrder` 值**(DatabaseInitializer 迁移 381,行 11820-11831): | LevelCode | level_order | |-----------|-------------| | FREE | 1 | | BASIC | 2 | | FAMILY | 3 | | PREMIUM | 4 | | LIFETIME | 5 | | PROVIDER | 1(与 FREE 同级,视为非会员)| **场景矩阵:** | 用户等级 | min_discount_level | userLevelOrder | minLevelOrder | 结果 | 预期 | |----------|-------------------|----------------|---------------|------|------| | FREE | NULL/空 | 1 | 2 | 原价 | ✅ | | FREE | "BASIC" | 1 | 2 | 原价 | ✅ | | FREE | "FAMILY" | 1 | 3 | 原价 | ✅ | | BASIC | NULL/空 | 2 | 2 | 会员价 | ✅ | | BASIC | "BASIC" | 2 | 2 | 会员价 | ✅ | | BASIC | "FAMILY" | 2 | 3 | 原价 | ✅ | | FAMILY | NULL/空 | 3 | 2 | 会员价 | ✅ | | PREMIUM | NULL/空 | 4 | 2 | 会员价 | ✅ | | PROVIDER | NULL/空 | 1 | 2 | 原价 | ✅ | | null buyerId | 任何 | 1(兜底) | 2 | 原价 | ✅ | **结论:✅ 通过** > **附注:测试数据与实际数据库不一致** > `MembershipServiceTest.java` 第 67 行将 `BASIC` mock 为 order=1(与 FREE 同级), > 但生产 DB 经迁移 381(行 11822-11826)后 `BASIC=2, FREE=1`。 > 此测试 mocking 不准确,但**不影响生产代码**——生产代码直接查询 DB,行为正确。 --- ### 核验项 2:所有调用点 | 调用点 | 文件:行号 | 方法 | 是否走 `getEligibleMemberPrice` | |--------|-----------|------|-------------------------------| | 单商品下单 | `ProductOrderService.java:263` | `create()` | ✅ `order.setUnitPrice(getEligibleMemberPrice(product, buyerId))` | | 单商品下单金额 | `ProductOrderService.java:264` | `create()` | ✅ `order.setTotalAmount(getEligibleMemberPrice(product, buyerId) * quantity)` | | 多商品下单(购物车) | `ProductOrderService.java:762` | `createMultiItem()` | ✅ `int unitPrice = getEligibleMemberPrice(product, buyerId)` | | 限时特价(秒杀) | `ProductOrderService.java:423` | `createSpecialDealOrder()` | ❌ 使用 `deal.getSpecialPrice()`,**不经过**本方法(设计如此) | **无绕过路径。** 所有结算价路径(单商品/多商品)均经 `getEligibleMemberPrice`。 #### CartService 第 339/358 行:无条件下发 memberPrice **代码:** `CartService.java` 行 338-358 ```java dto.setMemberPrice(product.getMemberPrice()); // 行 339(常规 SKU) dto.setMemberPrice(linkedProduct.getMemberPrice()); // 行 358(Linked SKU) ``` **评估:** - `memberPrice` 字段始终从 product 直接复制,**不过滤用户等级** - 同时 `unitPrice` 也设为 `product.getPrice()`(原价) - 注释(ProductOrderService.java 行 1741)明确:"会员体系重构:商品对所有用户统一定价,活动侧保留会员价,商品侧停用"——即**前端**自行判断是否展示会员价 **结论:** 这是一个**已有行为**,非本次改动引入;前端应通过 `minDiscountLevel` + 用户等级自行判断展示。 **是否需要修:** 建议由前端负责展示逻辑(当前设计如此)。后端不做过滤是正确的——避免端+后端双源判断不一致。 --- ### 核验项 3:`ActivityDTO.resolveMemberPrice` **代码位置:** `ActivityDTO.java` 第 137-146 行 ```java public static Integer resolveMemberPrice(Integer basePrice, Integer memberPrice, String memberLevel) { if (basePrice == null) return null; // If user has member level and a memberPrice is set, use memberPrice if (memberLevel != null && ("BASIC".equals(memberLevel) || "FAMILY".equals(memberLevel) || "PREMIUM".equals(memberLevel))) { if (memberPrice != null) { return memberPrice; } } return basePrice; } ``` **场景矩阵:** | memberLevel | 结果 | 预期 | |-------------|------|------| | null | basePrice | ✅ 非会员 | | "FREE" | basePrice | ✅ 免费用户不享 | | "BASIC" | memberPrice(若存在)| ✅ BASIC 纳入 | | "FAMILY" | memberPrice(若存在)| ✅ 保持不变 | | "PREMIUM" | memberPrice(若存在)| ✅ 保持不变 | | "PROVIDER" | basePrice | ✅ 服务商不享 | **调用方:** `ActivityService.java:117, 140` 均通过 `membershipService.getMemberLevel(userId)` 获取等级后传入,逻辑完整。 **结论:✅ 通过** --- ### 核验项 4:限时特价/秒杀路径 **代码位置:** `ProductOrderService.java` 行 343-488(`createSpecialDealOrder`) ```java // 行 423-424 order.setUnitPrice(deal.getSpecialPrice()); order.setTotalAmount(deal.getSpecialPrice() * quantity); ``` - 特价订单**完全不调用** `getEligibleMemberPrice` - 原价 = 特价(`deal.getSpecialPrice()`) - 该路径在本次任务变更前就已是如此(独立事务入口,规格 §5.2) **结论:✅ 通过**,特价路径未被影响。 --- ### 核验项 5:编译验证 ```bash mvn clean compile -q # 退出码:0(无输出 = 无错误) ``` **结论:✅ 通过** --- ## 三、发现的问题 ### 发现 1:测试 mock 不准确(不阻塞验收) **文件:** `MembershipServiceTest.java` 行 66-67 ```java doReturn(1).when(service).getLevelOrder("FREE"); // 测试中 FREE=1 doReturn(1).when(service).getLevelOrder("BASIC"); // 测试中 BASIC=1 ← 与生产不符 ``` **问题:** 测试将 BASIC mock 为 level_order=1(与 FREE 同级),导致 `getEligibleMemberPrice` 在测试环境中 FREE 和 BASIC 行为相同。 **实际生产值:** 迁移 381(行 11820-11831)确保 `BASIC=2, FREE=1`。 **影响:** 单元测试无法验证"FREE 不享会员价"的逻辑分支(在 mock 环境下不区分)。 **是否修复:** 本次核验不修改测试代码(测试 mock 是历史遗留,与需求实现无关)。建议后续由开发方修正测试数据。 --- ## 四、总结 | 核验项 | 结论 | |--------|------| | 1. getEligibleMemberPrice 逻辑正确性 | ✅ 通过 | | 2. ActivityDTO.resolveMemberPrice 纳入 BASIC | ✅ 通过 | | 3. 单商品/多商品调用点一致,无绕过 | ✅ 通过 | | 4. 限时特价/秒杀路径不受影响 | ✅ 通过 | | 5. mvn clean compile 编译通过 | ✅ 通过 | **是否建议验收:** ✅ **建议验收**。代码实现符合需求口径。BASIC 用户购买 `member_price` 设置且 `min_discount_level` 为 NULL/BASIC 的商品时获得会员价;FREE 用户获原价;FAMILY/PREMIUM 继续获会员价;`min_discount_level=FAMILY` 的商品 BASIC 用户获原价。唯一发现是单元测试 mock 数据与生产 DB 不一致(不影响生产行为)。