zhibo/Android弹幕接口对接分析报告.md

510 lines
16 KiB
Markdown
Raw Normal View History

2025-12-29 15:12:12 +08:00
# 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