zhibo/Android弹幕接口对接分析报告.md
2025-12-29 15:12:12 +08:00

510 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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. 测试验证历史消息正常显示
### 第二步: 实现在线人数WebSocket1-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