# WebSocket 集成说明

## 概述

在将商户店铺入住申请接口从 `alien-store` 迁移到 `alien-store-platform` 的过程中，需要集成 WebSocket 消息推送功能，用于实时通知商户申请已受理。

## 当前状态

### 已完成
- ✅ 在 `StoreManageServiceImpl.sendApplicationNotice()` 中添加了 WebSocket 消息推送的调用逻辑
- ✅ 创建了 `WebSocketUtil` 工具类作为占位实现
- ✅ 保存通知消息到数据库（`LifeNotice` 表）
- ✅ 构建了符合规范的 `WebSocketVo` 对象

### 待完成
- ⚠️ WebSocket 实际消息推送功能（当前仅记录日志）

## 问题说明

### 架构限制
`alien-store` 模块中的 `WebSocketProcess` 类位于 `shop.alien.store.config` 包下，`alien-store-platform` 模块无法直接引用：

```java
// alien-store 中的 WebSocketProcess
@ServerEndpoint(value = "/socket/{sendId}")
public class WebSocketProcess {
    public void sendMessage(String id, String message) throws Exception {
        // WebSocket 发送逻辑
    }
}
```

### 技术原因
- **模块隔离**：`alien-store-platform` 和 `alien-store` 是独立的微服务模块
- **依赖管理**：`alien-store-platform` 的 pom.xml 中未引入 `alien-store` 依赖
- **架构设计**：微服务间不应直接引用对方的实现类

## 解决方案

### 方案 1：将 WebSocketProcess 移到共享模块（推荐）⭐

**步骤：**
1. 将 `WebSocketProcess.java` 从 `alien-store` 移动到 `alien-config` 模块
2. 修改包路径为 `shop.alien.config.websocket.WebSocketProcess`
3. 在 `alien-store` 和 `alien-store-platform` 的 `pom.xml` 中都引用 `alien-config` 依赖（已存在）
4. 更新 `WebSocketUtil` 使用共享的 `WebSocketProcess`

**优点：**
- ✅ 代码复用，统一管理
- ✅ 符合微服务架构设计原则
- ✅ 两个服务都可以使用相同的 WebSocket 功能

**缺点：**
- ❌ 需要重构 `alien-store` 中的现有代码
- ❌ 可能影响到其他使用 WebSocket 的功能

**实现示例：**

```java
// alien-config/src/main/java/shop/alien/config/websocket/WebSocketProcess.java
package shop.alien.config.websocket;

@Slf4j
@Component
@ServerEndpoint(value = "/socket/{sendId}")
public class WebSocketProcess {
    // ... 原有实现保持不变
}

// alien-store-platform/.../WebSocketUtil.java
package shop.alien.storeplatform.util;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import shop.alien.config.websocket.WebSocketProcess;

@Component
@RequiredArgsConstructor
public class WebSocketUtil {
    private final WebSocketProcess webSocketProcess;
    
    public void sendMessage(String receiverId, String message) {
        webSocketProcess.sendMessage(receiverId, message);
    }
}
```

---

### 方案 2：通过消息队列异步推送

**步骤：**
1. 引入消息队列（RabbitMQ 或 Kafka）
2. `alien-store-platform` 发送消息到 MQ
3. `alien-store` 消费 MQ 消息并通过 WebSocket 推送

**优点：**
- ✅ 服务解耦，符合微服务架构
- ✅ 异步处理，不影响业务主流程
- ✅ 支持消息持久化和重试机制

**缺点：**
- ❌ 增加系统复杂度
- ❌ 需要引入和维护消息队列中间件
- ❌ 消息推送存在延迟

**实现示例：**

```java
// alien-store-platform - 生产者
@Component
@RequiredArgsConstructor
public class WebSocketUtil {
    private final RabbitTemplate rabbitTemplate;
    
    public void sendMessage(String receiverId, String message) {
        WebSocketMessage msg = new WebSocketMessage(receiverId, message);
        rabbitTemplate.convertAndSend("websocket.exchange", "websocket.route", msg);
    }
}

// alien-store - 消费者
@Component
@RequiredArgsConstructor
public class WebSocketMessageConsumer {
    private final WebSocketProcess webSocketProcess;
    
    @RabbitListener(queues = "websocket.queue")
    public void handleMessage(WebSocketMessage msg) {
        webSocketProcess.sendMessage(msg.getReceiverId(), msg.getMessage());
    }
}
```

---

### 方案 3：通过 Feign 调用 REST 接口

**步骤：**
1. 在 `alien-store` 中创建 WebSocket 发送的 REST 接口
2. 在 `alien-api` 中定义 Feign 客户端接口
3. `alien-store-platform` 通过 Feign 调用

**优点：**
- ✅ 实现简单直接
- ✅ 无需额外中间件
- ✅ 易于调试和监控

**缺点：**
- ❌ 增加 HTTP 调用开销
- ❌ 存在网络延迟
- ❌ 需要处理服务间调用失败的情况

**实现示例：**

```java
// alien-store - 提供 REST 接口
@RestController
@RequestMapping("/internal/websocket")
public class WebSocketInternalController {
    @Autowired
    private WebSocketProcess webSocketProcess;
    
    @PostMapping("/sendMessage")
    public R sendMessage(@RequestParam String receiverId, @RequestParam String message) {
        webSocketProcess.sendMessage(receiverId, message);
        return R.success();
    }
}

// alien-api - Feign 客户端
@FeignClient(name = "alien-store", path = "/internal/websocket")
public interface WebSocketFeignClient {
    @PostMapping("/sendMessage")
    R sendMessage(@RequestParam("receiverId") String receiverId, 
                  @RequestParam("message") String message);
}

// alien-store-platform - 使用 Feign
@Component
@RequiredArgsConstructor
public class WebSocketUtil {
    private final WebSocketFeignClient webSocketFeignClient;
    
    public void sendMessage(String receiverId, String message) {
        webSocketFeignClient.sendMessage(receiverId, message);
    }
}
```

---

## 当前实现

### WebSocketUtil（占位实现）

`alien-store-platform/src/main/java/shop/alien/storeplatform/util/WebSocketUtil.java`

```java
@Slf4j
@Component
public class WebSocketUtil {
    public void sendMessage(String receiverId, String message) {
        // TODO: 实现WebSocket消息推送
        log.info("WebSocketUtil.sendMessage - [占位实现] 准备发送消息: receiverId={}, message={}", 
                receiverId, message);
        log.warn("WebSocketUtil.sendMessage - WebSocket功能未完全实现，消息未实际推送");
    }
}
```

### 调用位置

`alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StoreManageServiceImpl.java`

```java
private void sendApplicationNotice(String userAccount) {
    // ... 构建通知消息
    
    // 1. 保存通知消息到数据库
    lifeNoticeMapper.insert(lifeNotice);
    
    // 2. 通过 WebSocket 实时推送消息
    WebSocketVo websocketVo = new WebSocketVo();
    websocketVo.setSenderId("system");
    websocketVo.setReceiverId(receiverId);
    websocketVo.setCategory("notice");
    websocketVo.setNoticeType("1");
    websocketVo.setIsRead(0);
    websocketVo.setText(JSONObject.from(lifeNotice).toJSONString());
    
    try {
        webSocketUtil.sendMessage(receiverId, JSONObject.from(websocketVo).toJSONString());
        log.info("WebSocket消息推送成功: receiverId={}", receiverId);
    } catch (Exception e) {
        log.error("WebSocket消息推送失败: {}", e.getMessage(), e);
    }
}
```

## 推荐实施步骤

### 短期方案（快速上线）
采用 **方案 3：Feign 调用**
1. 在 `alien-store` 中添加 WebSocket 发送的内部接口
2. 在 `alien-api` 中定义 Feign 客户端
3. 更新 `alien-store-platform` 的 `WebSocketUtil`
4. 测试验证

**预计时间：** 1-2 小时

### 长期方案（架构优化）
采用 **方案 1：共享模块**
1. 创建 `alien-config` 的 `websocket` 子包
2. 迁移 `WebSocketProcess` 到共享模块
3. 更新 `alien-store` 和 `alien-store-platform` 的引用
4. 全面回归测试

**预计时间：** 4-8 小时

## 测试验证

### 功能测试
1. 调用 `/storeManage/applyStore` 接口提交店铺入住申请
2. 检查数据库 `life_notice` 表是否插入通知记录
3. 检查 WebSocket 客户端是否收到实时消息
4. 验证消息内容格式和字段完整性

### 日志验证
```log
[INFO ] WebSocketUtil.sendMessage - [占位实现] 准备发送消息: receiverId=store_13800138000
[WARN ] WebSocketUtil.sendMessage - WebSocket功能未完全实现，消息未实际推送
```

## 相关文件

- `alien-store-platform/src/main/java/shop/alien/storeplatform/util/WebSocketUtil.java`
- `alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StoreManageServiceImpl.java`
- `alien-store/src/main/java/shop/alien/store/config/WebSocketProcess.java`
- `alien-entity/src/main/java/shop/alien/entity/store/vo/WebSocketVo.java`
- `alien-entity/src/main/java/shop/alien/entity/store/LifeNotice.java`

## 注意事项

1. ⚠️ **通知已保存到数据库**：即使 WebSocket 推送失败，通知消息也已保存到 `life_notice` 表，用户可以在消息列表中查看
2. ⚠️ **异常处理**：当前实现已捕获 WebSocket 推送异常，不会影响主业务流程
3. ⚠️ **日志记录**：占位实现会记录警告日志，便于后续排查和优化
4. ⚠️ **兼容性**：选择方案 1 时需要确保不影响 `alien-store` 现有的 WebSocket 功能

## 更新记录

- 2025-01-xx：初始创建，添加 WebSocket 占位实现
- 待定：实施正式的 WebSocket 推送方案

