510 lines
16 KiB
Markdown
510 lines
16 KiB
Markdown
# Android端直播间弹幕接口对接分析报告
|
||
|
||
> **分析时间**: 2024-12-29
|
||
> **分析范围**: 直播间弹幕和WebSocket实时通信功能
|
||
> **状态**: 🟡 部分完成,需要优化
|
||
|
||
---
|
||
|
||
## 📊 接口对接状态总览
|
||
|
||
| 功能模块 | 后端接口 | Android端实现 | 对接状态 | 问题说明 |
|
||
|---------|---------|--------------|---------|---------|
|
||
| 获取历史弹幕 | ✅ 已实现 | ✅ 已定义 | ⚠️ 未调用 | ApiService中已定义接口,但未在Activity中调用 |
|
||
| 发送弹幕消息 | ✅ 已实现 | ✅ 已定义 | ⚠️ 未调用 | ApiService中已定义接口,但未在Activity中调用 |
|
||
| WebSocket弹幕实时推送 | ✅ 已实现 | ✅ 已实现 | ✅ 已对接 | 使用WebSocket实时接收弹幕 |
|
||
| WebSocket在线人数统计 | ✅ 已实现 | ❌ 未实现 | ❌ 未对接 | 缺少在线人数WebSocket连接 |
|
||
|
||
---
|
||
|
||
## 🔍 详细分析
|
||
|
||
### 1. 获取历史弹幕接口 ⚠️
|
||
|
||
#### 后端接口
|
||
```
|
||
GET /api/front/live/public/rooms/{roomId}/messages
|
||
参数:
|
||
- roomId: 房间ID(路径参数)
|
||
- limit: 获取数量(查询参数,默认50)
|
||
返回: ApiResponse<List<ChatMessageResponse>>
|
||
```
|
||
|
||
**后端实现位置**: `LiveRoomController.java` 第105-116行
|
||
```java
|
||
@GetMapping("/public/rooms/{roomId}/messages")
|
||
public CommonResult<List<ChatMessageResponse>> getMessages(
|
||
@PathVariable Integer roomId,
|
||
@RequestParam(defaultValue = "50") Integer limit) {
|
||
if (roomId == null) return CommonResult.failed("参数错误");
|
||
List<LiveChat> messages = liveChatService.getRoomMessages(roomId, limit);
|
||
List<ChatMessageResponse> result = messages.stream()
|
||
.map(ChatMessageResponse::from)
|
||
.collect(Collectors.toList());
|
||
return CommonResult.success(result);
|
||
}
|
||
```
|
||
|
||
#### Android端实现
|
||
**接口定义**: `ApiService.java` 第67-71行
|
||
```java
|
||
@GET("api/front/live/public/rooms/{roomId}/messages")
|
||
Call<ApiResponse<List<ChatMessageResponse>>> getRoomMessages(
|
||
@Path("roomId") String roomId,
|
||
@Query("limit") int limit);
|
||
```
|
||
|
||
**问题**:
|
||
- ✅ ApiService中已定义接口
|
||
- ❌ RoomDetailActivity中未调用此接口
|
||
- ❌ 进入直播间时没有加载历史弹幕
|
||
- ⚠️ 目前使用模拟数据生成弹幕(`startChatSimulation()`方法)
|
||
|
||
**TODO标记**: `RoomDetailActivity.java` 第514行
|
||
```java
|
||
// TODO: 接入后端接口 - 初始化时获取历史弹幕消息
|
||
// 接口路径: GET /api/rooms/{roomId}/messages
|
||
```
|
||
|
||
---
|
||
|
||
### 2. 发送弹幕消息接口 ⚠️
|
||
|
||
#### 后端接口
|
||
```
|
||
POST /api/front/live/public/rooms/{roomId}/messages
|
||
参数:
|
||
- roomId: 房间ID(路径参数)
|
||
- message: 消息内容(请求体)
|
||
- visitorId: 访客ID(请求体,可选)
|
||
- nickname: 昵称(请求体,可选)
|
||
返回: ApiResponse<ChatMessageResponse>
|
||
```
|
||
|
||
**后端实现位置**: `LiveRoomController.java` 第118-143行
|
||
```java
|
||
@PostMapping("/public/rooms/{roomId}/messages")
|
||
public CommonResult<ChatMessageResponse> sendMessage(
|
||
@PathVariable Integer roomId,
|
||
@RequestBody Map<String, String> body) {
|
||
String content = body.get("message");
|
||
String visitorId = body.get("visitorId");
|
||
String nickname = body.get("nickname");
|
||
// ... 保存消息逻辑
|
||
}
|
||
```
|
||
|
||
#### Android端实现
|
||
**接口定义**: `ApiService.java` 第73-76行
|
||
```java
|
||
@POST("api/front/live/public/rooms/{roomId}/messages")
|
||
Call<ApiResponse<ChatMessageResponse>> sendRoomMessage(
|
||
@Path("roomId") String roomId,
|
||
@Body Map<String, String> body);
|
||
```
|
||
|
||
**问题**:
|
||
- ✅ ApiService中已定义接口
|
||
- ❌ RoomDetailActivity中未调用此接口
|
||
- ✅ 目前使用WebSocket发送弹幕(`sendChatViaWebSocket()`方法)
|
||
- ⚠️ 建议保留HTTP接口作为WebSocket失败时的降级方案
|
||
|
||
---
|
||
|
||
### 3. WebSocket弹幕实时推送 ✅
|
||
|
||
#### 后端WebSocket端点
|
||
```
|
||
ws://localhost:8080/ws/live/chat/{roomId}
|
||
功能:
|
||
- 实时弹幕消息广播
|
||
- 消息格式: JSON
|
||
- 敏感词过滤
|
||
```
|
||
|
||
**后端实现位置**:
|
||
- WebSocket配置: `WebSocketConfig.java`
|
||
- 消息处理: `LiveChatWebSocketHandler.java`
|
||
|
||
#### Android端实现
|
||
|
||
**方式1: RoomDetailActivity直接实现** ✅
|
||
- 位置: `RoomDetailActivity.java` 第87-88行
|
||
- WebSocket URL: `ws://192.168.1.164:8081/ws/live/chat/{roomId}`
|
||
- 连接方法: `connectWebSocket()` 第249-308行
|
||
- 发送方法: `sendChatViaWebSocket()` 第383-401行
|
||
- 心跳机制: `startHeartbeat()` 第311-329行
|
||
|
||
**方式2: LiveChatClient封装** ✅
|
||
- 位置: `LiveChatClient.java`
|
||
- 提供了完整的WebSocket客户端封装
|
||
- 支持连接、发送、接收、断开等操作
|
||
|
||
**状态**: ✅ 已完整实现
|
||
- ✅ WebSocket连接正常
|
||
- ✅ 消息发送和接收正常
|
||
- ✅ 心跳保活机制完善
|
||
- ✅ 自动重连机制完善
|
||
- ✅ 错误处理完善
|
||
|
||
---
|
||
|
||
### 4. WebSocket在线人数统计 ❌
|
||
|
||
#### 后端WebSocket端点
|
||
```
|
||
ws://localhost:8080/ws/live/{roomId}?clientId={clientId}
|
||
功能:
|
||
- 实时在线人数统计
|
||
- 心跳保活机制
|
||
- 用户去重
|
||
```
|
||
|
||
**后端实现位置**:
|
||
- WebSocket配置: `LiveRoomWebSocketConfig.java`
|
||
- 在线人数服务: `LiveRoomOnlineServiceImpl.java`
|
||
- 消息处理: `LiveRoomWebSocketHandler.java`
|
||
|
||
**后端接口**:
|
||
```java
|
||
// 获取在线人数
|
||
GET /api/live/online/count/{roomId}
|
||
|
||
// 手动广播人数
|
||
POST /api/live/online/broadcast/{roomId}
|
||
```
|
||
|
||
#### Android端实现
|
||
**状态**: ❌ 未实现
|
||
|
||
**问题**:
|
||
- ❌ 没有连接在线人数WebSocket
|
||
- ❌ 没有发送心跳保活
|
||
- ❌ 没有接收在线人数更新
|
||
- ⚠️ 目前使用模拟数据显示观看人数
|
||
|
||
**TODO标记**: `RoomDetailActivity.java` 第656行
|
||
```java
|
||
// TODO: 接入后端接口 - 获取实时观看人数
|
||
// 接口路径: GET /api/rooms/{roomId}/viewers/count
|
||
// 建议使用WebSocket实时推送观看人数变化,或每10-15秒轮询一次
|
||
```
|
||
|
||
---
|
||
|
||
## 🔧 需要修复的问题
|
||
|
||
### 问题1: 未加载历史弹幕 🔴 高优先级
|
||
|
||
**影响**: 用户进入直播间时看不到之前的弹幕消息
|
||
|
||
**解决方案**:
|
||
```java
|
||
// 在 RoomDetailActivity.onCreate() 或 onStart() 中添加
|
||
private void loadHistoryMessages() {
|
||
if (TextUtils.isEmpty(roomId)) return;
|
||
|
||
ApiClient.getService(this).getRoomMessages(roomId, 50)
|
||
.enqueue(new Callback<ApiResponse<List<ChatMessageResponse>>>() {
|
||
@Override
|
||
public void onResponse(Call<ApiResponse<List<ChatMessageResponse>>> call,
|
||
Response<ApiResponse<List<ChatMessageResponse>>> response) {
|
||
if (response.isSuccessful() && response.body() != null
|
||
&& response.body().isOk()) {
|
||
List<ChatMessageResponse> messages = response.body().getData();
|
||
if (messages != null) {
|
||
for (ChatMessageResponse msg : messages) {
|
||
addChatMessage(new ChatMessage(
|
||
msg.getNickname(),
|
||
msg.getContent()
|
||
));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public void onFailure(Call<ApiResponse<List<ChatMessageResponse>>> call,
|
||
Throwable t) {
|
||
Log.e("RoomDetail", "加载历史弹幕失败: " + t.getMessage());
|
||
}
|
||
});
|
||
}
|
||
```
|
||
|
||
**调用位置**: 在`connectWebSocket()`之前调用,确保先显示历史消息
|
||
|
||
---
|
||
|
||
### 问题2: 未实现在线人数WebSocket ⚠️ 中优先级
|
||
|
||
**影响**: 无法实时显示观看人数变化
|
||
|
||
**解决方案**:
|
||
```java
|
||
// 1. 添加在线人数WebSocket连接
|
||
private WebSocket onlineWebSocket;
|
||
private static final String WS_ONLINE_BASE_URL = "ws://192.168.1.164:8081/ws/live/";
|
||
|
||
private void connectOnlineWebSocket() {
|
||
if (TextUtils.isEmpty(roomId)) return;
|
||
|
||
// 生成唯一的clientId
|
||
String clientId = AuthStore.getUserId(this) + "_" + System.currentTimeMillis();
|
||
|
||
Request request = new Request.Builder()
|
||
.url(WS_ONLINE_BASE_URL + roomId + "?clientId=" + clientId)
|
||
.build();
|
||
|
||
onlineWebSocket = wsClient.newWebSocket(request, new WebSocketListener() {
|
||
@Override
|
||
public void onOpen(WebSocket webSocket, okhttp3.Response response) {
|
||
Log.d("OnlineWS", "在线人数WebSocket连接成功");
|
||
// 启动心跳
|
||
startOnlineHeartbeat();
|
||
}
|
||
|
||
@Override
|
||
public void onMessage(WebSocket webSocket, String text) {
|
||
try {
|
||
JSONObject json = new JSONObject(text);
|
||
String type = json.optString("type", "");
|
||
|
||
if ("online_count".equals(type)) {
|
||
int count = json.optInt("count", 0);
|
||
handler.post(() -> {
|
||
binding.topViewerCount.setText(String.valueOf(count));
|
||
});
|
||
} else if ("pong".equals(type)) {
|
||
Log.d("OnlineWS", "收到在线人数心跳响应");
|
||
}
|
||
} catch (JSONException e) {
|
||
Log.e("OnlineWS", "解析消息失败: " + e.getMessage());
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public void onFailure(WebSocket webSocket, Throwable t, okhttp3.Response response) {
|
||
Log.e("OnlineWS", "在线人数WebSocket连接失败: " + t.getMessage());
|
||
}
|
||
|
||
@Override
|
||
public void onClosed(WebSocket webSocket, int code, String reason) {
|
||
Log.d("OnlineWS", "在线人数WebSocket关闭: " + reason);
|
||
}
|
||
});
|
||
}
|
||
|
||
// 2. 在线人数心跳
|
||
private Runnable onlineHeartbeatRunnable;
|
||
|
||
private void startOnlineHeartbeat() {
|
||
stopOnlineHeartbeat();
|
||
onlineHeartbeatRunnable = new Runnable() {
|
||
@Override
|
||
public void run() {
|
||
if (onlineWebSocket != null) {
|
||
try {
|
||
JSONObject ping = new JSONObject();
|
||
ping.put("type", "ping");
|
||
onlineWebSocket.send(ping.toString());
|
||
Log.d("OnlineWS", "发送在线人数心跳");
|
||
} catch (JSONException e) {
|
||
Log.e("OnlineWS", "发送心跳失败: " + e.getMessage());
|
||
}
|
||
handler.postDelayed(onlineHeartbeatRunnable, 25000); // 25秒心跳
|
||
}
|
||
}
|
||
};
|
||
handler.postDelayed(onlineHeartbeatRunnable, 25000);
|
||
}
|
||
|
||
private void stopOnlineHeartbeat() {
|
||
if (onlineHeartbeatRunnable != null) {
|
||
handler.removeCallbacks(onlineHeartbeatRunnable);
|
||
onlineHeartbeatRunnable = null;
|
||
}
|
||
}
|
||
|
||
// 3. 断开在线人数WebSocket
|
||
private void disconnectOnlineWebSocket() {
|
||
stopOnlineHeartbeat();
|
||
if (onlineWebSocket != null) {
|
||
onlineWebSocket.close(1000, "Activity destroyed");
|
||
onlineWebSocket = null;
|
||
}
|
||
}
|
||
```
|
||
|
||
**调用位置**:
|
||
- `onStart()`: 调用`connectOnlineWebSocket()`
|
||
- `onStop()`: 调用`disconnectOnlineWebSocket()`
|
||
|
||
---
|
||
|
||
### 问题3: WebSocket URL硬编码 🟡 低优先级
|
||
|
||
**影响**: 切换环境时需要修改代码
|
||
|
||
**当前代码**: `RoomDetailActivity.java` 第87行
|
||
```java
|
||
private static final String WS_BASE_URL = "ws://192.168.1.164:8081/ws/live/chat/";
|
||
```
|
||
|
||
**解决方案**: 使用ApiConfig统一管理
|
||
```java
|
||
// 在 ApiConfig.java 中添加
|
||
public static String getWebSocketBaseUrl() {
|
||
return BASE_URL.replace("http://", "ws://")
|
||
.replace("https://", "wss://");
|
||
}
|
||
|
||
// 在 RoomDetailActivity.java 中使用
|
||
private String getWsChatUrl() {
|
||
return ApiConfig.getWebSocketBaseUrl() + "/ws/live/chat/";
|
||
}
|
||
|
||
private String getWsOnlineUrl() {
|
||
return ApiConfig.getWebSocketBaseUrl() + "/ws/live/";
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 问题4: 缺少HTTP接口降级方案 🟡 低优先级
|
||
|
||
**影响**: WebSocket失败时无法发送弹幕
|
||
|
||
**解决方案**: 在WebSocket发送失败时使用HTTP接口
|
||
```java
|
||
private void sendChatViaWebSocket(String content) {
|
||
if (webSocket == null || !isWebSocketConnected) {
|
||
// WebSocket未连接,使用HTTP接口发送
|
||
sendChatViaHttp(content);
|
||
return;
|
||
}
|
||
|
||
try {
|
||
JSONObject json = new JSONObject();
|
||
json.put("type", "chat");
|
||
json.put("content", content);
|
||
json.put("nickname", AuthStore.getNickname(this));
|
||
json.put("userId", AuthStore.getUserId(this));
|
||
|
||
boolean sent = webSocket.send(json.toString());
|
||
if (!sent) {
|
||
// 发送失败,使用HTTP接口
|
||
sendChatViaHttp(content);
|
||
}
|
||
} catch (JSONException e) {
|
||
Log.e("WebSocket", "发送消息失败: " + e.getMessage());
|
||
// 失败时使用HTTP接口
|
||
sendChatViaHttp(content);
|
||
}
|
||
}
|
||
|
||
private void sendChatViaHttp(String content) {
|
||
Map<String, String> body = new HashMap<>();
|
||
body.put("message", content);
|
||
body.put("visitorId", AuthStore.getUserId(this));
|
||
body.put("nickname", AuthStore.getNickname(this));
|
||
|
||
ApiClient.getService(this).sendRoomMessage(roomId, body)
|
||
.enqueue(new Callback<ApiResponse<ChatMessageResponse>>() {
|
||
@Override
|
||
public void onResponse(Call<ApiResponse<ChatMessageResponse>> call,
|
||
Response<ApiResponse<ChatMessageResponse>> response) {
|
||
if (response.isSuccessful() && response.body() != null
|
||
&& response.body().isOk()) {
|
||
// 本地显示发送的消息
|
||
addChatMessage(new ChatMessage("我", content));
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public void onFailure(Call<ApiResponse<ChatMessageResponse>> call,
|
||
Throwable t) {
|
||
Toast.makeText(RoomDetailActivity.this,
|
||
"发送失败,请检查网络", Toast.LENGTH_SHORT).show();
|
||
}
|
||
});
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 📋 修复优先级
|
||
|
||
| 优先级 | 问题 | 预计工作量 | 影响范围 |
|
||
|-------|------|-----------|---------|
|
||
| 🔴 高 | 加载历史弹幕 | 30分钟 | 用户体验 |
|
||
| ⚠️ 中 | 在线人数WebSocket | 1-2小时 | 功能完整性 |
|
||
| 🟡 低 | WebSocket URL配置 | 15分钟 | 代码质量 |
|
||
| 🟡 低 | HTTP降级方案 | 30分钟 | 稳定性 |
|
||
|
||
---
|
||
|
||
## ✅ 修复步骤建议
|
||
|
||
### 第一步: 加载历史弹幕(30分钟)
|
||
1. 在`RoomDetailActivity`中添加`loadHistoryMessages()`方法
|
||
2. 在`onStart()`中调用,在`connectWebSocket()`之前
|
||
3. 测试验证历史消息正常显示
|
||
|
||
### 第二步: 实现在线人数WebSocket(1-2小时)
|
||
1. 添加在线人数WebSocket连接方法
|
||
2. 实现心跳保活机制
|
||
3. 处理在线人数更新消息
|
||
4. 在`onStart()`和`onStop()`中管理连接
|
||
5. 测试验证在线人数实时更新
|
||
|
||
### 第三步: 优化WebSocket URL配置(15分钟)
|
||
1. 在`ApiConfig`中添加WebSocket URL方法
|
||
2. 修改`RoomDetailActivity`使用动态URL
|
||
3. 测试不同环境切换
|
||
|
||
### 第四步: 添加HTTP降级方案(30分钟)
|
||
1. 实现`sendChatViaHttp()`方法
|
||
2. 在WebSocket失败时自动降级
|
||
3. 测试降级场景
|
||
|
||
---
|
||
|
||
## 📊 对接完成度评估
|
||
|
||
| 功能 | 完成度 | 说明 |
|
||
|------|-------|------|
|
||
| WebSocket弹幕实时推送 | 100% | ✅ 完全实现 |
|
||
| 发送弹幕消息 | 90% | ✅ WebSocket实现,缺少HTTP降级 |
|
||
| 获取历史弹幕 | 50% | ⚠️ 接口已定义,未调用 |
|
||
| 在线人数统计 | 0% | ❌ 完全未实现 |
|
||
| **总体完成度** | **60%** | ⚠️ 核心功能已实现,需要完善 |
|
||
|
||
---
|
||
|
||
## 🎯 总结
|
||
|
||
### 已完成 ✅
|
||
1. ✅ WebSocket弹幕实时推送(完整实现)
|
||
2. ✅ 弹幕发送功能(WebSocket方式)
|
||
3. ✅ 心跳保活机制
|
||
4. ✅ 自动重连机制
|
||
5. ✅ 接口定义完整
|
||
|
||
### 待完成 ⚠️
|
||
1. ⚠️ 加载历史弹幕(高优先级)
|
||
2. ⚠️ 在线人数WebSocket(中优先级)
|
||
3. ⚠️ HTTP接口降级方案(低优先级)
|
||
4. ⚠️ WebSocket URL配置优化(低优先级)
|
||
|
||
### 建议
|
||
1. **优先修复历史弹幕加载**,这对用户体验影响最大
|
||
2. **实现在线人数WebSocket**,完善功能完整性
|
||
3. **添加HTTP降级方案**,提高系统稳定性
|
||
4. **统一WebSocket URL管理**,提升代码质量
|
||
|
||
---
|
||
|
||
**报告生成时间**: 2024-12-29
|
||
**分析人员**: Kiro AI Assistant
|