# 注销店铺流程风险分析报告

## 一、已修复的问题 ✅

1. **时间不一致问题**：定时任务从8天改为7天，与通知一致
2. **重新入驻问题**：清理商户用户注销状态，允许重新入驻
3. **身份验证问题**：入驻申请时排除已注销的店铺

---

## 二、潜在风险分析 ⚠️

### 1. **事务性问题** 🔴 高风险

**问题描述**：
- 定时任务删除店铺时没有使用 `@Transactional` 注解
- 如果删除过程中某个步骤失败，可能导致数据不一致

**影响范围**：
- 店铺已删除，但商户用户注销状态未清理 → 无法重新入驻
- 店铺已删除，但粉丝关系未清理 → 数据冗余
- 店铺已删除，但Redis地理位置未清理 → 搜索结果包含已删除店铺

**风险场景**：
```java
// 当前代码：没有事务保护
storeUser.setLogoutFlag(0);
storeUserMapper.updateById(storeUser);  // 如果这里失败
storeInfoMapper.deleteById(storeInfo.getId());  // 店铺已删除，但用户状态未更新
```

**建议修复**：
```java
@Transactional(rollbackFor = Exception.class)
public void cancellationOfBusinessJob() {
    // 删除逻辑
}
```

---

### 2. **异常处理缺失** 🔴 高风险

**问题描述**：
- 定时任务没有 try-catch 异常处理
- 单个店铺删除失败会导致整个任务中断
- 后续店铺无法被删除

**影响范围**：
- 如果第一个店铺删除失败，后续所有店铺都不会被处理
- 错误信息无法记录，难以排查问题

**风险场景**：
```java
// 当前代码：没有异常处理
for (StoreInfo storeInfo : storeInfos) {
    // 如果这里抛出异常，整个循环中断
    storeInfoMapper.deleteById(storeInfo.getId());
}
```

**建议修复**：
```java
for (StoreInfo storeInfo : storeInfos) {
    try {
        // 删除逻辑
    } catch (Exception e) {
        log.error("删除店铺失败: storeId={}, error={}", storeInfo.getId(), e.getMessage(), e);
        // 继续处理下一个店铺
    }
}
```

---

### 3. **关联数据清理不完整** 🟡 中风险

**问题描述**：
- 当前只清理了：StoreUser、LifeFans、Redis Token
- 未清理的数据：
  - Redis 地理位置信息（GEO）
  - 订单数据（LifeUserOrder）
  - 优惠券数据（LifeCoupon、LifeDiscountCoupon）
  - 店铺图片（StoreImg）
  - 评论数据（StoreComment）
  - 通知消息（LifeNotice）
  - 其他业务关联数据

**影响范围**：
- Redis GEO 中残留已删除店铺 → 搜索结果包含已删除店铺
- 订单数据残留 → 数据统计不准确
- 图片数据残留 → 存储空间浪费

**建议修复**：
参考 `MerchantUserServiceImpl.resetToInitialStatus` 的完整清理逻辑，补充以下清理：
```java
// 1. 清理Redis地理位置
nearMeService.removeGeolocation(Boolean.TRUE, storeInfo.getId().toString());

// 2. 清理订单数据（根据业务需求决定是否物理删除）
// 3. 清理优惠券数据
// 4. 清理店铺图片
// 5. 清理评论数据
// 6. 清理通知消息
```

---

### 4. **取消注销逻辑不完善** 🟡 中风险

**问题描述**：
- `cancelLogoutStore` 方法没有检查是否超过7天
- 如果超过7天，店铺可能已被定时任务删除，取消注销会失败

**影响范围**：
- 用户在第8天尝试取消注销，但店铺已被删除
- 可能导致 `NullPointerException` 或其他异常

**当前代码**：
```java
public void cancelLogoutStore(StoreInfoVo storeInfo) {
    StoreInfo storeIn = storeInfoMapper.selectOne(...);
    // 没有检查 storeIn 是否为 null
    // 没有检查是否超过7天
    storeIn.setLogoutFlag(0);
    // ...
}
```

**建议修复**：
```java
public void cancelLogoutStore(StoreInfoVo storeInfo) {
    StoreInfo storeIn = storeInfoMapper.selectOne(...);
    if (storeIn == null) {
        throw new BusinessException("店铺不存在或已被删除");
    }
    
    // 检查是否超过7天
    if (storeIn.getLogoutTime() != null) {
        Date logoutTime = storeIn.getLogoutTime();
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(logoutTime);
        calendar.add(Calendar.DAY_OF_YEAR, 7);
        if (new Date().after(calendar.getTime())) {
            throw new BusinessException("已超过7天冷静期，无法取消注销");
        }
    }
    
    // 取消注销逻辑
}
```

---

### 5. **并发安全问题** 🟡 中风险

**问题描述**：
- 定时任务没有分布式锁保护
- 如果多个实例同时执行，可能重复删除

**影响范围**：
- 多实例部署时，可能重复执行删除操作
- 虽然 MyBatis-Plus 的 `deleteById` 是幂等的，但清理操作可能重复执行

**建议修复**：
```java
@XxlJob("cancellationOfBusinessJob")
public void cancellationOfBusinessJob() {
    String lockKey = "job:cancellationOfBusinessJob";
    String lockValue = baseRedisService.lock(lockKey, 300000, 10000); // 5分钟锁，10秒超时
    if (lockValue == null) {
        log.warn("获取分布式锁失败，跳过本次执行");
        return;
    }
    
    try {
        // 删除逻辑
    } finally {
        baseRedisService.unlock(lockKey, lockValue);
    }
}
```

---

### 6. **时间精度问题** 🟢 低风险

**问题描述**：
- 使用 `Date` 和 `Calendar` 进行时间比较
- 可能存在时区或精度问题

**影响范围**：
- 不同时区可能导致判断不准确
- 毫秒级精度可能导致边界情况

**建议修复**：
使用 `LocalDateTime` 或 `Instant` 进行时间计算，更精确：
```java
LocalDateTime logoutTime = storeInfo.getLogoutTime().toInstant()
    .atZone(ZoneId.systemDefault())
    .toLocalDateTime();
LocalDateTime sevenDaysLater = logoutTime.plusDays(7);
LocalDateTime now = LocalDateTime.now();
if (now.isAfter(sevenDaysLater) || now.isEqual(sevenDaysLater)) {
    // 删除逻辑
}
```

---

### 7. **日志记录不完整** 🟢 低风险

**问题描述**：
- 删除操作缺少详细的日志记录
- 无法追踪删除历史和问题排查

**建议修复**：
```java
log.info("开始删除店铺: storeId={}, storeName={}, logoutTime={}, relatedUsers={}", 
    storeInfo.getId(), storeInfo.getStoreName(), logoutTime, relatedStoreUsers.size());
// ... 删除操作
log.info("店铺删除完成: storeId={}, deletedUsers={}, deletedFans={}", 
    storeInfo.getId(), deletedUsers, deletedFans);
```

---

## 三、修复优先级

| 优先级 | 风险项 | 影响 | 修复难度 |
|--------|--------|------|----------|
| P0 | 事务性问题 | 数据不一致 | 低 |
| P0 | 异常处理缺失 | 任务中断 | 低 |
| P1 | 关联数据清理不完整 | 数据冗余 | 中 |
| P1 | 取消注销逻辑不完善 | 用户体验 | 低 |
| P2 | 并发安全问题 | 重复执行 | 中 |
| P3 | 时间精度问题 | 边界情况 | 低 |
| P3 | 日志记录不完整 | 问题排查 | 低 |

---

## 四、建议修复方案

### 方案1：完整修复（推荐）

1. 添加事务保护
2. 添加异常处理
3. 补充关联数据清理
4. 完善取消注销逻辑
5. 添加分布式锁
6. 优化时间计算
7. 完善日志记录

### 方案2：快速修复（最小改动）

1. 添加异常处理（最重要）
2. 添加事务保护
3. 完善取消注销逻辑

---

## 五、测试建议

1. **单元测试**：
   - 测试7天边界情况（6天23小时59分 vs 7天0分1秒）
   - 测试删除过程中的异常情况

2. **集成测试**：
   - 测试定时任务执行
   - 测试取消注销功能
   - 测试重新入驻功能

3. **压力测试**：
   - 测试大量店铺同时删除
   - 测试并发执行情况

---

## 六、监控建议

1. **定时任务监控**：
   - 监控任务执行时间
   - 监控删除数量
   - 监控失败次数

2. **数据一致性监控**：
   - 监控 `logoutFlag=1` 且超过7天的店铺数量
   - 监控 Redis GEO 中的店铺数量

3. **告警设置**：
   - 定时任务执行失败告警
   - 删除数量异常告警
