注销店铺流程完整性分析.md 8.7 KB

注销店铺流程完整性分析报告

一、流程概览

1. 商家提交注销申请 (logoutStore)
   ↓
2. 设置店铺状态为注销中 (storeStatus=-1, logoutFlag=1)
   ↓
3. 发送通知(7天冷静期)
   ↓
4. 7天内可撤回 (cancelLogoutStore)
   ↓
5. 超过7天后,定时任务自动删除店铺数据 (cancellationOfBusinessJob)

二、当前实现分析

1. 注销申请接口 ✅

文件: StoreInfoServiceImpl.java:2691-2726

实现逻辑:

  • ✅ 设置 storeStatus = -1(注销中)
  • ✅ 设置 logoutFlag = 1(注销标记)
  • ✅ 记录 logoutTime(注销申请时间)
  • ✅ 记录 logoutReason(注销原因)
  • ✅ 记录 logoutCode(注销code)
  • ✅ 发送WebSocket通知

问题:

  • ⚠️ 通知内容说"7天冷静期",但定时任务判断是"8天后"删除(不一致)

2. 取消注销接口 ✅

文件: StoreInfoServiceImpl.java:2743-2773

实现逻辑:

  • ✅ 恢复 logoutFlag = 0
  • ✅ 恢复 storeStatus = 1(可用)
  • ✅ 清空注销相关字段
  • ✅ 发送撤回通知

问题:

  • 缺少状态检查:没有检查是否还在冷静期内(理论上超过7天就不应该允许撤回)
  • 缺少时间验证:没有验证 logoutTime 是否超过7天

3. 定时任务删除 ⚠️

文件: StoreMembershipCardJob.java:122-198

实现逻辑:

  • ✅ 查询 logoutFlag = 1 的店铺
  • ✅ 判断 logoutTime 是否超过8天
  • ✅ 删除店铺记录
  • ✅ 清空 StoreUser.storeId
  • ✅ 删除粉丝关系
  • ✅ 删除Redis token

问题:

  • 时间不一致:通知说7天冷静期,但判断是8天后删除(应该改为7天)
  • 数据清理不完整:只清理了基础数据,缺少以下关联数据:
    • 订单数据(life_user_order
    • 订单-优惠券中间表(order_coupon_middle
    • 优惠券(life_discount_coupon
    • 代金券(life_coupon
    • 用户领取的优惠券(life_discount_coupon_user
    • 团购券(life_group_buy_main
    • 评论(store_commentlife_comment
    • 通知消息(life_notice
    • 店铺图片(store_img
    • 店铺标签关系(tag_store_relation
    • 其他关联数据
  • 缺少事务保护:没有 @Transactional,可能导致部分数据删除失败
  • 缺少异常处理:没有 try-catch,一个店铺删除失败会影响其他店铺
  • 缺少详细日志:删除操作缺少详细的日志记录
  • 缺少状态检查:删除前没有再次检查 logoutFlag 是否为1(可能已被撤回)

三、发现的问题汇总

🔴 严重问题

  1. 数据清理不完整

    • 定时任务只清理了基础数据(StoreInfo、StoreUser、LifeFans、Redis)
    • 缺少订单、优惠券、商品、评论等大量关联数据的清理
    • 参考 MerchantUserServiceImpl.resetToInitialStatus 应该清理更多数据
  2. 时间逻辑不一致

    • 通知说"7天冷静期"
    • 定时任务判断"8天后"删除
    • 应该统一为7天
  3. 缺少事务保护

    • 定时任务没有事务,可能导致部分数据删除失败,造成数据不一致

🟡 中等问题

  1. 缺少状态检查

    • 取消注销时没有检查是否还在冷静期内
    • 定时任务删除前没有再次检查 logoutFlag 是否为1
  2. 缺少异常处理

    • 定时任务没有 try-catch,一个店铺删除失败会影响其他店铺
    • 应该每个店铺单独处理,失败不影响其他
  3. 缺少详细日志

    • 删除操作缺少详细的日志记录
    • 无法追踪删除过程和失败原因

🟢 轻微问题

  1. 代码可读性
    • 定时任务中变量命名 sevenDay 实际是8天后的时间,容易误导

四、建议修复方案

1. 修复时间逻辑

// 当前代码(错误)
calendar.add(Calendar.DAY_OF_YEAR, 8);  // 8天后删除

// 应该改为
calendar.add(Calendar.DAY_OF_YEAR, 7);  // 7天后删除

2. 完善数据清理

参考 MerchantUserServiceImpl.resetToInitialStatus,添加以下清理逻辑:

// 1. 查询订单ID列表
List<LifeUserOrder> orders = lifeUserOrderMapper.selectList(
    new LambdaQueryWrapper<LifeUserOrder>().eq(LifeUserOrder::getStoreId, storeId)
);
List<String> orderIds = orders.stream().map(o -> o.getId().toString()).collect(Collectors.toList());

// 2. 删除订单-优惠券中间表
if (!orderIds.isEmpty()) {
    orderCouponMiddleMapper.delete(
        new LambdaQueryWrapper<OrderCouponMiddle>().in(OrderCouponMiddle::getOrderId, orderIds)
    );
}

// 3. 删除订单
lifeUserOrderMapper.delete(
    new LambdaQueryWrapper<LifeUserOrder>().eq(LifeUserOrder::getStoreId, storeId)
);

// 4. 删除优惠券
lifeDiscountCouponMapper.delete(
    new LambdaQueryWrapper<LifeDiscountCoupon>().eq(LifeDiscountCoupon::getStoreId, storeId)
);

// 5. 删除代金券
lifeCouponMapper.delete(
    new LambdaQueryWrapper<LifeCoupon>().eq(LifeCoupon::getStoreId, storeId)
);

// 6. 删除团购券
lifeGroupBuyMainMapper.delete(
    new LambdaQueryWrapper<LifeGroupBuyMain>().eq(LifeGroupBuyMain::getStoreId, storeId)
);

// 7. 删除通知消息
lifeNoticeMapper.delete(
    new LambdaQueryWrapper<LifeNotice>().eq(LifeNotice::getReceiverId, "store_" + storeUser.getPhone())
);

// 8. 删除店铺图片
storeImgMapper.delete(
    new LambdaQueryWrapper<StoreImg>().eq(StoreImg::getStoreId, storeId)
);

// 9. 删除标签关系
tagStoreRelationMapper.delete(
    new LambdaQueryWrapper<TagStoreRelation>().eq(TagStoreRelation::getStoreId, storeId)
);

// 10. 删除评论(根据实际表结构调整)
// storeCommentMapper.delete(...)
// lifeCommentMapper.delete(...)

3. 添加事务保护

@XxlJob("cancellationOfBusinessJob")
@Transactional(rollbackFor = Exception.class)
public void cancellationOfBusinessJob() {
    // ... 删除逻辑
}

4. 添加异常处理

for (StoreInfo storeInfo : storeInfos) {
    try {
        // 再次检查状态
        if (storeInfo.getLogoutFlag() != 1) {
            log.warn("店铺 {} 的注销状态已变更,跳过删除", storeInfo.getId());
            continue;
        }
        
        // 检查时间
        if (storeInfo.getLogoutTime() == null) {
            log.warn("店铺 {} 的注销时间为空,跳过删除", storeInfo.getId());
            continue;
        }
        
        // 计算7天后的时间
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(storeInfo.getLogoutTime());
        calendar.add(Calendar.DAY_OF_YEAR, 7);  // 改为7天
        Date sevenDayLater = calendar.getTime();
        
        if (new Date().compareTo(sevenDayLater) >= 0) {
            // 执行删除逻辑
            deleteStoreData(storeInfo);
            log.info("成功删除店铺 {} 及其关联数据", storeInfo.getId());
        }
    } catch (Exception e) {
        log.error("删除店铺 {} 失败: {}", storeInfo.getId(), e.getMessage(), e);
        // 继续处理下一个店铺,不中断整个任务
    }
}

5. 添加取消注销时的状态检查

public void cancelLogoutStore(StoreInfoVo storeInfo) {
    StoreInfo storeIn = storeInfoMapper.selectOne(
        new LambdaQueryWrapper<StoreInfo>().eq(StoreInfo::getId, storeInfo.getId())
    );
    
    if (storeIn == null) {
        throw new IllegalArgumentException("店铺不存在");
    }
    
    // 检查是否还在冷静期内
    if (storeIn.getLogoutTime() != null) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(storeIn.getLogoutTime());
        calendar.add(Calendar.DAY_OF_YEAR, 7);
        Date sevenDayLater = calendar.getTime();
        
        if (new Date().compareTo(sevenDayLater) >= 0) {
            throw new IllegalStateException("已超过7天冷静期,无法撤回注销申请");
        }
    }
    
    // 检查当前状态
    if (storeIn.getLogoutFlag() != 1) {
        throw new IllegalStateException("店铺未处于注销状态,无法撤回");
    }
    
    // ... 后续恢复逻辑
}

五、修复优先级

  1. P0(必须修复)

    • 修复时间逻辑(7天 vs 8天)
    • 完善数据清理逻辑
    • 添加异常处理
  2. P1(重要)

    • 添加事务保护
    • 添加状态检查
    • 添加详细日志
  3. P2(优化)

    • 代码可读性优化
    • 性能优化(批量删除)

六、测试建议

  1. 单元测试

    • 测试注销申请后状态是否正确
    • 测试7天内取消注销是否成功
    • 测试7天后取消注销是否失败
    • 测试定时任务是否正确删除数据
  2. 集成测试

    • 测试完整注销流程
    • 测试数据清理是否完整
    • 测试异常情况处理
  3. 数据验证

    • 验证删除后数据库中是否还有残留数据
    • 验证关联数据是否全部清理