成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

就靠這手 WebSocket 抽象術,我們讓 N 條業務線消息飛起來了

開發 前端
如果你也正在被多個業務線的 WebSocket 開發問題困擾,不妨試試我們的抽象術,說不定能讓你的消息也飛起來。記得把這篇文章轉發給你的同事和朋友,讓更多的人免受重復造輪子之苦。

兄弟們,不知道你們有沒有過這樣的體驗:當你正沉浸在代碼的世界里,享受著摸魚前的最后一段寧靜時光時,產品經理突然帶著慈祥的笑容拍了拍你的肩膀:"小王啊,咱們這次要搞個大的,用戶端需要同時支持實時聊天、訂單提醒、直播互動、設備監控這四個業務線的消息推送哦,記得用 WebSocket 實現哈,明天早上要看到 Demo 哦~"

那一刻,你的內心是不是像被一萬只草泥馬奔騰而過?啥?四個業務線?每個業務線的消息格式、交互邏輯、連接管理都不一樣,難道要寫四套 WebSocket 代碼?那以后再來新的業務線怎么辦?難道每次都要從零開始?想想公司那祖傳的代碼庫里,已經堆了十幾套不同的 WebSocket 實現,每次維護都像在拆炸彈,你就頭皮發麻。

別慌,今天哥就來給大家分享一下我們團隊是如何靠一手 WebSocket 抽象術,讓 N 條業務線的消息像脫韁的野馬一樣自由飛翔的。這可是我們踩了無數個坑,掉了無數根頭發總結出來的實戰經驗,干貨滿滿,建議先收藏再慢慢看,說不定哪天就能救你于水火之中。

一、在抽象之前:被按在地上摩擦的日子

故事要從半年前說起,當時我們團隊接手了一個大型項目,需要支持多個業務線的實時消息推送。一開始,我們想著每個業務線獨立開發 WebSocket 服務,這樣簡單直接,開發速度也快。于是,我們很快就搞出了幾個獨立的 WebSocket 模塊:

  • 聊天模塊:用 JSON 格式傳輸消息,需要處理用戶上下線通知、消息回執、群聊廣播等功能。
  • 訂單模塊:用 PROTOBUF 格式傳輸訂單數據,需要處理訂單狀態變更通知、庫存預警等,對實時性和可靠性要求極高。
  • 直播模塊:需要處理彈幕實時推送、禮物特效同步、觀眾互動消息等,還涉及到多房間隔離、流量控制等問題。

一開始,這些獨立的模塊運行得還不錯,畢竟業務簡單,代碼量也不大。但是隨著業務的快速擴張,問題就來了:

1. 代碼重復率高達 90%

每個業務線都要寫一遍 WebSocket 的連接處理、消息解析、心跳檢測、異常處理等代碼。比如,心跳檢測,每個模塊都要寫一個定時任務,每隔一段時間向客戶端發送心跳包,還要處理客戶端的心跳響應。消息解析更是麻煩,每個業務線的消息格式不一樣,都要寫一套獨立的解析邏輯,JSON 的、PROTOBUF 的、甚至還有自定義二進制格式的。

2. 維護成本爆炸式增長

每當需要升級 WebSocket 的底層功能,比如支持新的協議、優化性能、增加安全策略時,每個業務線都要單獨修改,單獨測試。有一次,我們發現了一個 WebSocket 連接泄漏的問題,結果在每個業務線的代碼里都找了一遍,花了整整兩天時間才解決。更要命的是,不同業務線的開發人員水平參差不齊,寫出來的代碼質量相差很大,有的模塊里充滿了各種魔法值和硬編碼,簡直就是定時炸彈。

3. 業務擴展舉步維艱

當新的業務線來了,開發人員需要從零開始熟悉 WebSocket 的開發流程,還要處理各種底層細節,開發周期被無限拉長。而且,不同業務線之間的消息交互變得非常困難,比如聊天模塊需要給訂單模塊的用戶發送通知,結果發現兩個模塊的 WebSocket 協議完全不兼容,根本無法直接通信,只能通過中間件來中轉,增加了系統的復雜度和延遲。

4. 客戶端苦不堪言

想象一下,客戶端需要同時連接多個 WebSocket 服務,每個業務線一個連接。手機屏幕就那么大,電量就那么點,同時保持多個長連接,不僅耗電快,還占用網絡資源,導致手機發熱、流量消耗過快,用戶體驗極差。而且,每個客戶端都要處理多個 WebSocket 的連接狀態管理、消息接收分發,代碼量激增,兼容性問題頻出。

看著這些問題像滾雪球一樣越滾越大,我們終于意識到:不能再這樣下去了,必須搞一套通用的 WebSocket 抽象框架,把這些重復的、底層的功能統一起來,讓業務線開發人員只需要關注業務邏輯,而不需要關心 WebSocket 的底層實現。

二、WebSocket 抽象術的核心思想:打造消息宇宙的 "中轉站"

我們的目標很明確:讓每個業務線都像 plug - and - play 的插件一樣,只需要實現自己的業務邏輯,就能輕松接入到統一的 WebSocket 服務中。而這個統一的框架,就像是一個 "消息宇宙中轉站",負責處理所有的底層連接管理、協議轉換、消息路由、異常處理等工作。

1. 分層架構設計:各司其職,互不干擾

我們把整個框架分為三層:基礎設施層、抽象中間層、業務實現層。

(1)基礎設施層:扎實的地基

這一層是整個框架的底層支撐,負責處理最基礎的 WebSocket 通信細節,包括:

  • 連接管理:支持多種連接方式(TCP、WSS 等),處理連接的建立、斷開、重連等邏輯,維護連接池,支持海量連接的并發處理。我們用 Netty 作為底層通信框架,利用它的高性能 IO 處理能力,輕松應對萬級甚至十萬級的并發連接。
  • 協議解析:支持多種消息協議(JSON、PROTOBUF、自定義二進制協議等),通過工廠模式動態創建不同的協議解析器。比如,當接收到一個消息時,先讀取消息頭中的協議類型字段,然后根據這個類型創建對應的解析器,將字節流轉換成業務可識別的消息對象。
  • 心跳機制:統一實現心跳檢測功能,支持客戶端和服務端雙向心跳。服務端定時向客戶端發送心跳包,如果一段時間內沒有收到客戶端的心跳響應,就認為連接失效,主動斷開連接并觸發重連機制。心跳包的格式也可以自定義,比如在消息頭中增加一個心跳標志位。
  • 安全認證:集成 JWT 認證、IP 白名單、SSL/TLS 加密等安全策略,確保 WebSocket 連接的安全性。客戶端在連接時,需要攜帶認證信息,服務端進行驗證通過后,才允許建立連接。

(2)抽象中間層:萬能的適配器

這一層是整個框架的核心,定義了一套通用的接口和規范,讓業務層只需要關注業務邏輯,而不需要關心底層細節。主要包括:

  • 業務處理器接口:定義了業務處理的標準流程,包括消息接收處理、連接建立處理、連接斷開處理等方法。每個業務線只需要實現這個接口,就能處理自己的業務邏輯。比如:
public interface WebSocketBusinessHandler {
    // 連接建立時的處理
    void onConnect(WebSocketSession session, Map<String, String> params);
    // 接收消息時的處理
    void onMessage(WebSocketSession session, Object message);
    // 連接斷開時的處理
    void onDisconnect(WebSocketSession session, CloseStatus closeStatus);
}
  • 消息路由機制:根據消息中的業務標識(比如每個消息都有一個業務類型字段,如 "chat"、"order"、"live" 等),將消息路由到對應的業務處理器進行處理。我們維護了一個業務處理器注冊表,當業務線注冊時,將業務標識和對應的處理器關聯起來。當收到消息時,先解析出業務標識,然后從注冊表中找到對應的處理器,調用其 onMessage 方法。
  • 協議轉換層:將基礎設施層解析出來的消息對象,轉換成業務層可識別的格式,同時也能將業務層的響應消息轉換成適合發送給客戶端的格式。比如,業務層處理完消息后,返回一個自定義的 Response 對象,協議轉換層會根據客戶端支持的協議(JSON 或 PROTOBUF),將 Response 對象序列化成對應的字節流,然后通過基礎設施層發送給客戶端。

(3)業務實現層:專注業務邏輯的舞臺

這一層就是各個業務線的開發人員發揮的地方了,他們只需要按照抽象中間層定義的接口,實現自己的業務邏輯即可。比如,聊天業務線的開發人員,只需要在 onMessage 方法中處理聊天消息的邏輯,如存儲消息、廣播給其他用戶等;訂單業務線的開發人員,只需要處理訂單狀態變更的通知邏輯,如查詢訂單詳情、發送通知給用戶等。

2. 協議抽象:讓不同語言的 "外星人" 也能交流

在實際項目中,客戶端可能有多種類型,比如 Android、iOS、Web、小程序,甚至還有一些物聯網設備,它們可能使用不同的編程語言和消息協議。為了讓這些 "外星人" 都能在我們的消息宇宙中自由交流,我們對消息協議進行了抽象,定義了一套統一的消息格式:

{
    "protocol": "ws-protocol-v1", // 協議版本
    "bizType": "chat", // 業務類型
    "msgType": "text", // 消息類型(文本、圖片、文件等)
    "msgId": "123456", // 消息唯一標識
    "timestamp": 1685260800000, // 消息時間戳
    "data": { // 業務具體數據
        "sender": "user1",
        "receiver": "user2",
        "content": "你好啊"
    },
    "extra": { // 擴展字段
        "roomId": "1001",
        "deviceType": "android"
    }
}

所有客戶端和服務端之間的消息都必須遵循這個格式,這樣不管客戶端使用什么協議,在傳輸過程中都會被轉換成這個統一格式,然后再分發給對應的業務處理器。比如,Android 客戶端使用 PROTOBUF 協議發送消息,服務端的協議解析器會先將 PROTOBUF 格式的字節流轉換成這個統一的 JSON 格式對象,然后再根據 bizType 路由到對應的業務處理器。業務處理器處理完后,返回的響應消息也是這個統一格式,協議轉換層會根據客戶端支持的協議,將其轉換成 PROTOBUF 或 JSON 格式的字節流發送給客戶端。

3. 連接管理:讓每個連接都有自己的 "戶口本"

為了更好地管理海量的 WebSocket 連接,我們為每個連接創建了一個 "連接上下文" 對象,就像是每個連接的 "戶口本",里面記錄了連接的所有相關信息,包括:

  • WebSocketSession 對象(Netty 中的連接句柄)
  • 用戶標識(如果客戶端已經認證)
  • 業務類型(該連接屬于哪個業務線)
  • 設備類型(Android、iOS、Web 等)
  • 連接創建時間、最后活躍時間
  • 自定義擴展字段(比如直播間 ID、用戶等級等)

我們維護了一個全局的連接注冊表,以連接 ID 為鍵,連接上下文為值。當需要向某個用戶發送消息時,只需要根據用戶標識和業務類型,從注冊表中找到對應的連接上下文,然后通過基礎設施層發送消息即可。同時,連接上下文還會用于心跳檢測、權限控制、消息路由等功能。比如,在進行權限控制時,我們可以根據連接上下文中的用戶等級,決定是否允許該用戶接收某些類型的消息。

三、核心實現細節:從 0 到 1 打造抽象框架

1. 業務處理器的注冊與發現

我們使用 Spring 的 Bean 管理機制來實現業務處理器的注冊。每個業務線的處理器類只需要加上 @WebSocketBusinessHandler 注解,并指定 bizType 即可:

@WebSocketBusinessHandler(bizType = "chat")
public class ChatBusinessHandler implements WebSocketBusinessHandler {
    // 實現業務處理方法
}

框架啟動時,會掃描所有帶有 @WebSocketBusinessHandler 注解的 Bean,將 bizType 和對應的處理器對象存入業務處理器注冊表中。當收到消息時,從消息中提取 bizType,然后從注冊表中查找對應的處理器,調用其 onMessage 方法。這種方式實現了業務處理器的自動注冊和發現,業務線開發人員不需要手動進行注冊,大大簡化了開發流程。

2. 消息的編解碼實現

我們基于 Netty 的 ChannelHandler 實現了自定義的編解碼器:

(1)消息解碼流程

  • 讀取消息頭,獲取協議類型、消息長度、業務類型等信息。
  • 根據協議類型選擇對應的解碼器(JSON 解碼器或 PROTOBUF 解碼器)。
  • 解碼消息體,轉換成統一的消息對象格式。
  • 驗證消息的合法性,比如消息長度是否正確、業務類型是否存在對應的處理器等。如果驗證不通過,發送錯誤響應給客戶端,并關閉連接。

(2)消息編碼流程

  • 接收業務處理器返回的響應消息(統一格式的消息對象)。
  • 根據客戶端支持的協議(在連接建立時協商確定)選擇對應的編碼器。
  • 將消息對象編碼成字節流,加上消息頭,發送給客戶端。

3. 心跳機制的優化

為了減少心跳包對網絡資源的占用,我們實現了自適應的心跳間隔機制:

  • 初始心跳間隔為 30 秒。
  • 如果連續三次收到客戶端的心跳響應,說明網絡狀況良好,心跳間隔自動延長到 60 秒。
  • 如果出現一次心跳超時,心跳間隔縮短為 15 秒,以便更快地檢測到連接是否失效。
  • 如果連續兩次心跳超時,認為連接失效,主動斷開連接,并觸發重連機制。

同時,我們在心跳包中攜帶一些額外的信息,比如服務端的時間戳、當前連接的狀態等,客戶端可以根據這些信息進行一些業務相關的處理,比如同步客戶端的時間。

4. 異常處理機制

在 WebSocket 通信過程中,可能會出現各種異常,比如網絡中斷、消息格式錯誤、業務處理異常等。我們實現了統一的異常處理機制:

  • 對于可恢復的異常(如臨時網絡波動),自動進行重連,并記錄異常日志。
  • 對于不可恢復的異常(如消息格式錯誤且無法解析),發送錯誤響應給客戶端,詳細說明錯誤原因,然后關閉連接。
  • 在業務處理器中,開發人員可以通過拋出自定義異常來觸發特定的異常處理邏輯,比如當用戶權限不足時,拋出 PermissionDeniedException,框架會自動發送權限不足的錯誤響應給客戶端。

四、落地實踐:從理論到現實的跨越

1. 多業務線接入實戰

以我們的聊天業務線和訂單業務線為例,看看它們是如何接入到抽象框架中的:

(1)聊天業務線

  • 實現 WebSocketBusinessHandler 接口,處理消息接收時的邏輯:
@WebSocketBusinessHandler(bizType = "chat")
public class ChatBusinessHandler implements WebSocketBusinessHandler {
    @Autowired
    private ChatService chatService;
    @Override
    public void onConnect(WebSocketSession session, Map<String, String> params) {
        // 解析參數,獲取用戶ID、房間ID等
        String userId = params.get("userId");
        String roomId = params.get("roomId");
        // 將連接加入房間的連接列表
        chatService.addRoomConnection(roomId, session);
        // 發送連接成功通知給客戶端
        Map<String, Object> responseData = new HashMap<>();
        responseData.put("status", "success");
        responseData.put("message", "連接聊天服務成功");
        WebSocketMessage message = WebSocketMessage.builder()
               .bizType("chat")
               .msgType("system")
               .data(responseData)
               .build();
        WebSocketSender.send(session, message);
    }
    @Override
    public void onMessage(WebSocketSession session, Object message) {
        // 將Object轉換為聊天消息對象
        ChatMessage chatMessage = (ChatMessage) message;
        // 存儲聊天消息
        chatService.saveMessage(chatMessage);
        // 廣播給房間內的其他用戶
        chatService.broadcastMessage(chatMessage.getRoomId(), chatMessage, session);
    }
    @Override
    public void onDisconnect(WebSocketSession session, CloseStatus closeStatus) {
        // 從房間的連接列表中移除連接
        String roomId = getRoomIdFromSession(session); // 自定義方法獲取房間ID
        chatService.removeRoomConnection(roomId, session);
    }
}

(2)訂單業務線

  • 實現 WebSocketBusinessHandler 接口,處理訂單狀態變更通知:
@WebSocketBusinessHandler(bizType = "order")
public class OrderBusinessHandler implements WebSocketBusinessHandler {
    @Autowired
    private OrderService orderService;
    @Override
    public void onConnect(WebSocketSession session, Map<String, String> params) {
        // 解析參數,獲取用戶ID
        String userId = params.get("userId");
        // 訂閱該用戶的訂單狀態變更事件
        orderService.subscribeOrderEvents(userId, session);
    }
    @Override
    public void onMessage(WebSocketSession session, Object message) {
        // 將Object轉換為訂單操作消息對象
        OrderOperationMessage operationMessage = (OrderOperationMessage) message;
        // 根據操作類型進行處理,比如取消訂單、支付訂單等
        if ("cancel".equals(operationMessage.getOperationType())) {
            orderService.cancelOrder(operationMessage.getOrderId());
        } else if ("pay".equals(operationMessage.getOperationType())) {
            orderService.payOrder(operationMessage.getOrderId());
        }
    }
    @Override
    public void onDisconnect(WebSocketSession session, CloseStatus closeStatus) {
        // 解析用戶ID,取消訂閱
        String userId = getUserIdFromSession(session); // 自定義方法獲取用戶ID
        orderService.unsubscribeOrderEvents(userId, session);
    }
}

2. 性能優化實戰

在接入多個業務線后,我們遇到了性能瓶頸,主要是在消息廣播和海量連接處理方面。我們采取了以下優化措施:

(1)異步處理

對于一些耗時的業務操作(如數據庫存儲、第三方接口調用等),我們使用 Spring 的 @Async 注解進行異步處理,避免阻塞 WebSocket 的 IO 線程。比如,在聊天業務線的 onMessage 方法中,存儲消息的操作是異步執行的,這樣不會影響消息的廣播速度。

(2)批量處理

對于需要向大量用戶發送消息的場景(如直播房間的彈幕廣播),我們使用批量發送機制,將多個消息合并成一個批量消息包,減少網絡 IO 次數。同時,利用 Netty 的 ChannelPipeline 的 flush 機制,控制發送速率,避免瞬間發送大量消息導致網絡擁塞。

(3)連接分片

當連接數達到十萬級時,我們將連接注冊表按照業務類型和用戶 ID 進行分片,使用 ConcurrentHashMap 的分段鎖機制,提高并發訪問效率。比如,將聊天業務的連接和訂單業務的連接分別存儲在不同的 Map 中,每個 Map 再按照用戶 ID 的哈希值進行分片,每個分片獨立加鎖,減少鎖競爭。

3. 多端兼容實戰

為了支持 Android、iOS、Web、小程序等多端客戶端,我們做了以下工作:

(1)統一客戶端 SDK

開發了多語言的客戶端 SDK,封裝了 WebSocket 的連接管理、消息編解碼、心跳處理等底層邏輯,客戶端只需要引入 SDK,調用簡單的 API 即可接入。SDK 提供了統一的回調接口,讓客戶端可以方便地處理消息接收、連接狀態變更等事件。

(2)協議協商機制

在連接建立時,客戶端和服務端通過握手階段協商支持的協議(JSON 或 PROTOBUF)、消息壓縮方式(GZIP 或 DEFLATE)等參數。服務端根據客戶端的能力,選擇最合適的通信方式,確保兼容性和性能。

(3)錯誤碼統一

定義了一套統一的錯誤碼體系,客戶端和服務端都使用相同的錯誤碼和錯誤信息,方便問題排查和定位。比如,錯誤碼 1001 表示認證失敗,1002 表示消息格式錯誤,客戶端可以根據錯誤碼做出相應的處理,如提示用戶重新登錄、顯示錯誤信息等。

五、收益與挑戰:踩坑路上的經驗總結

1. 顯著的收益

  • 開發效率提升 80%:業務線開發人員不需要再處理 WebSocket 的底層細節,只需要專注于業務邏輯,開發周期從原來的兩周縮短到兩天。
  • 維護成本降低 60%:統一的框架使得底層功能的升級和維護變得簡單,只需要在框架層進行一次修改,所有業務線自動生效。
  • 客戶端體驗大幅提升:統一的連接管理減少了客戶端的連接數,節省了電量和網絡資源,同時 SDK 的使用讓客戶端開發更加簡單,兼容性問題減少了 70%。
  • 業務擴展能力增強:新業務線的接入變得非常容易,就像安裝一個插件一樣,只需要實現一個業務處理器,注冊一下 bizType 即可,真正實現了 "即插即用"。

2. 遇到的挑戰

  • 協議兼容性問題:在支持多種協議和多端客戶端時,遇到了很多協議格式不兼容的問題,比如 PROTOBUF 的不同版本之間的兼容性、JSON 中日期格式的處理等。我們通過制定嚴格的協議規范和版本管理機制,逐步解決了這些問題。
  • 性能調優的持續性:隨著業務量的增長,性能問題會不斷出現,比如海量連接下的內存占用問題、高并發下的消息處理延遲問題等。這需要我們持續進行性能監控和調優,建立完善的性能優化體系。
  • 跨團隊協作的溝通成本:當多個團隊共同使用這個框架時,需要統一開發規范和接口文檔,確保大家對框架的理解一致。我們通過定期的技術分享會、編寫詳細的開發文檔和示例代碼,降低了跨團隊協作的溝通成本。

六、結語:讓消息飛一會兒

回顧整個 WebSocket 抽象框架的研發過程,我們經歷了從被問題按在地上摩擦到實現華麗轉身的過程。這一路上,我們踩過的坑、掉過的頭發,最終都變成了寶貴的經驗,凝結成了這個強大的抽象框架。

現在,當新的業務線來了,我們再也不用慌慌張張地寫重復代碼了,只需要輕松地實現一個業務處理器,注冊一下,就能讓新業務線的消息在我們的消息宇宙中自由飛翔。看著 N 條業務線的消息像漫天繁星一樣在我們的框架中閃爍,那種成就感,簡直比搞定產品經理的無理需求還要爽一百倍。

當然,技術的發展是永無止境的,我們的框架也在不斷進化。未來,我們計劃引入 AI 技術,實現智能的消息路由和流量控制;支持更多的通信協議,如 MQTT、STOMP 等,以滿足物聯網設備的接入需求;進一步優化性能,支持百萬級甚至千萬級的并發連接。

如果你也正在被多個業務線的 WebSocket 開發問題困擾,不妨試試我們的抽象術,說不定能讓你的消息也飛起來。記得把這篇文章轉發給你的同事和朋友,讓更多的人免受重復造輪子之苦。

責任編輯:武曉燕 來源: 石杉的架構筆記
相關推薦

2025-03-28 03:20:00

MySQL數據庫搜索

2020-09-29 07:54:05

Express 飛起

2011-04-13 10:51:58

MATLAB

2025-06-04 01:35:00

RocketMQ異步消息

2023-11-10 18:03:04

業務場景SQL

2021-07-13 07:52:03

SQL面試COUNT(*)

2022-09-02 08:21:24

idea插件

2013-01-07 09:34:43

CodeLoveBAT

2011-02-25 08:39:11

QFabric數據中心Juniper

2019-03-25 08:05:35

Elasticsear優化集群

2016-01-19 17:03:59

數據中心網絡華為

2025-04-15 00:00:00

2019-11-05 10:35:57

SpringBoot調優Java

2024-11-27 09:46:34

2025-01-17 09:23:31

2018-05-15 11:05:36

Wifi速度數字

2019-07-02 05:36:16

WiFi網速寬帶

2023-03-01 23:59:23

Java開發

2011-09-27 13:25:05

Web

2024-06-12 12:28:23

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 香蕉视频久久久 | 免费观看av | 欧美日韩一区在线观看 | 国产片侵犯亲女视频播放 | 中文字幕一区二区三区四区五区 | 国产激情在线 | 亚洲综合色视频在线观看 | 一区观看| 一级一片在线观看 | 91免费入口 | 成人国产在线视频 | 欧美老妇交乱视频 | 欧美在线观看一区 | 91丨九色丨国产在线 | 欧美日韩一区二区在线观看 | 欧美久久久网站 | 亚洲高清视频在线观看 | 中文在线亚洲 | 日韩免费 | 精品日韩| 一片毛片 | 91国内视频在线 | 久久久久国产视频 | 久久精品国产亚洲一区二区三区 | 手机在线一区二区三区 | 狠狠干天天干 | 久久激情五月丁香伊人 | 天天操夜夜操免费视频 | 欧美成人精品一区二区男人看 | 天天综合久久 | 欧美xxxⅹ性欧美大片 | 狠狠草视频 | 欧美在线观看一区 | 午夜精品久久久久久久久久久久久 | 日韩欧美一区二区三区四区 | 四虎永久 | 久久久99精品免费观看 | 中文一区| 久久激情网 | 日韩精品免费在线观看 | 日本成人在线免费视频 |