exec-task6.md 6.9 KB

任务6验证报告:指定商品会员价机制

执行时间: 2026-10-24
执行智能体: 任务6专用
核验状态: 待独立核验


一、需求回顾(来自 2026-10-23-basic-membership-spec-revision.md)

需求口径(第4条)

# 权益 口径 对应实现
4 指定商品会员价 享受(原无) discount_purchase 生效:products.member_price + min_discount_level(NULL 时默认 BASIC)

任务6步骤

  • ProductOrderService.getEligibleMemberPrice:min_discount_level 为 NULL 时默认按 BASIC 判定(修复 FREE 也可享的漏洞);等级比较用 getLevelOrder
  • ActivityDTO.resolveMemberPrice:会员等级集合纳入 BASIC(FAMILY/PREMIUM 保持不变;PROVIDER 仍不享)
  • mvn clean compile 通过

验收标准

  • BASIC 购买"设置 member_price 且 min_discount_level 为空/BASIC"的商品得会员价
  • FREE 得原价
  • FAMILY+ 得会员价
  • min_discount_level=FAMILY 的商品 BASIC 得原价
  • 活动(activities)会员价对 BASIC 生效

二、逐条验收结论

验收项 1:ProductOrderService.getEligibleMemberPrice

代码位置: ProductOrderService.java 第 2166-2188 行

当前实现:

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);
        }
    }
    // 产品未设置最低级别时默认 BASIC
    String minLevel = (product.getMinDiscountLevel() == null || product.getMinDiscountLevel().isEmpty())
            ? "BASIC" : product.getMinDiscountLevel();
    // 比较级别数字:级别数字越小越低
    int userLevelOrder = membershipService.getLevelOrder(userLevel);
    int minLevelOrder = membershipService.getLevelOrder(minLevel);
    // 用户级别 >= 最低要求级别,则可享会员价
    if (userLevelOrder >= minLevelOrder) {
        return product.getMemberPrice();
    }
    return product.getPrice();
}

逻辑分析:

场景 min_discount_level userLevel userLevelOrder minLevelOrder 结果
FREE 买 min_level=NULL 商品 NULL → "BASIC" "FREE" 1 2 ❌ FREE < BASIC → 原价 ✓
BASIC 买 min_level=NULL 商品 NULL → "BASIC" "BASIC" 2 2 ✅ BASIC >= BASIC → 会员价 ✓
FAMILY 买 min_level=NULL 商品 NULL → "BASIC" "FAMILY" 3 2 ✅ FAMILY >= BASIC → 会员价 ✓
BASIC 买 min_level=FAMILY 商品 "FAMILY" "BASIC" 2 3 ❌ BASIC < FAMILY → 原价 ✓
FAMILY 买 min_level=FAMILY 商品 "FAMILY" "FAMILY" 3 3 ✅ FAMILY >= FAMILY → 会员价 ✓

结论:✅ PASS

  • min_discount_level 为 NULL 或空字符串时,默认按 BASIC 判定 ✓
  • 使用 getLevelOrder 进行等级比较 ✓
  • FREE 用户不再能享受会员价(已修复漏洞)✓
  • 处理了空字符串边界情况(比草稿更严谨)✓

验收项 2:ActivityDTO.resolveMemberPrice

代码位置: ActivityDTO.java 第 137-146 行

当前实现:

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 / "" ❌ 非会员
"FREE" ❌ 免费用户不享受
"BASIC" ✅ 9.9元体验会员纳入 ✓
"FAMILY" ✅ 保持不变 ✓
"PREMIUM" ✅ 保持不变 ✓
"PROVIDER" ❌ 服务商不享受 ✓

结论:✅ PASS

  • BASIC 已纳入会员等级集合 ✓
  • FAMILY 与 PREMIUM 保持不变 ✓
  • PROVIDER 仍然不享受会员价 ✓

验收项 3:两个调用点行为一致

调用点1: ProductOrderService.java:263(单商品下单)

order.setUnitPrice(getEligibleMemberPrice(product, buyerId));

调用点2: ProductOrderService.java:762(多商品下单/购物车)

int unitPrice = getEligibleMemberPrice(product, buyerId);

结论:✅ PASS
两个调用点均调用同一方法 getEligibleMemberPrice,逻辑完全一致。


验收项 4:不波及特价活动流程

特价订单流程(createSpecialDealOrder):

// 第422-423行
order.setUnitPrice(deal.getSpecialPrice());
order.setTotalAmount(deal.getSpecialPrice() * quantity);

特价订单直接使用 deal.getSpecialPrice() 作为单价,完全不调用 getEligibleMemberPrice。

结论:✅ PASS
特价覆盖会员价的原有逻辑保持不变。


验收项 5:Java 8 兼容 + 编译通过

  • 代码使用标准 Java 8 语法,无 var、List.of() 等新特性
  • mvn clean compile -q 退出码为 0,编译通过

结论:✅ PASS


三、发现的缺陷与修复

发现的缺陷

原草稿实现:

String minLevel = product.getMinDiscountLevel() != null ? product.getMinDiscountLevel() : "BASIC";

问题: 未处理 min_discount_level 为空字符串("")的情况。虽然数据库字段为 VARCHAR(50) 且默认值为 NULL,但为保险起见应同时处理空字符串。

修复后的代码:

String minLevel = (product.getMinDiscountLevel() == null || product.getMinDiscountLevel().isEmpty())
        ? "BASIC" : product.getMinDiscountLevel();

影响: 增强了代码的健壮性,防止未来数据异常时出现 NPE 或逻辑错误。


四、mvn 编译关键输出

EXIT_CODE=0

编译成功,无任何 error 或 warning。


五、git diff --stat

 cfc-backend/src/main/java/com/etotem/cfc/dto/ActivityDTO.java       | 5 +++--
 cfc-backend/src/main/java/com/etotem/cfc/service/ProductOrderService.java | 16 ++++++++--------
 2 files changed, 11 insertions(+), 10 deletions(-)

六、总结

验收项 结论
1. getEligibleMemberPrice 逻辑正确性 ✅ PASS
2. ActivityDTO.resolveMemberPrice 纳入 BASIC ✅ PASS
3. 单商品/多商品调用点一致 ✅ PASS
4. 特价活动流程不受影响 ✅ PASS
5. Java 8 兼容 + 编译通过 ✅ PASS

任务状态: ✅ 已完成,等待独立核验智能体验收