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

16 KiB
Raw Blame History

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行

@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行

@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行

// 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行

@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行

@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

后端接口:

// 获取在线人数
GET /api/live/online/count/{roomId}

// 手动广播人数
POST /api/live/online/broadcast/{roomId}

Android端实现

状态: 未实现

问题:

  • 没有连接在线人数WebSocket
  • 没有发送心跳保活
  • 没有接收在线人数更新
  • ⚠️ 目前使用模拟数据显示观看人数

TODO标记: RoomDetailActivity.java 第656行

// TODO: 接入后端接口 - 获取实时观看人数
// 接口路径: GET /api/rooms/{roomId}/viewers/count
// 建议使用WebSocket实时推送观看人数变化或每10-15秒轮询一次

🔧 需要修复的问题

问题1: 未加载历史弹幕 🔴 高优先级

影响: 用户进入直播间时看不到之前的弹幕消息

解决方案:

// 在 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 ⚠️ 中优先级

影响: 无法实时显示观看人数变化

解决方案:

// 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行

private static final String WS_BASE_URL = "ws://192.168.1.164:8081/ws/live/chat/";

解决方案: 使用ApiConfig统一管理

// 在 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接口

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