起因 為什么做這個東西,是突然間聽一后端同事說起Annie這個東西,發現這個東西下載視頻挺方便的,會自動爬取網頁中的視頻,然后整理成列表。發現用命令執行之后是下面的樣子: 心里琢磨了下,整一個界面玩一下吧。然后就做成下面這個樣子了。 列表 下載列表 本文地址倉庫://github.com/Rynxiao/yh-tools,如果喜歡,歡迎star. 涉及技術 Express 后端服務 Webpack 模塊化編譯工具 Nginx 主要做文件gzip壓縮(發現Express添加gzip有點問題,才棄坑nginx) Ant-design 前端UI庫 React + React Router WebSocket 進度回傳服務 其中還有點小插曲,最開始是使用docker起了一個nginx服務,但是發現內部轉發一直有問題,同時獲取宿主主機IP也出現了點問題,然后折磨了好久放棄了。(docker研究不深,敬請諒解^_^) 下載部分細節 首先瀏覽器會連接WebSocket服務器,同時在WebSocket服務器上存在一個所有客戶端的Map,瀏覽器端生成一個uuid作為瀏覽器客戶端id,然后將這個鏈接作為值存進Map中。 客戶端: // list.jsx await WebSocketClient.connect((event) => { const data = JSON.parse(event.data); if (data.event === 'close') { this.updateCloseStatusOfProgressBar(list, data); } else { this.generateProgressBarList(list, data); } }); // src/utils/websocket.client.js async connect(onmessage, onerror) { const socket = this.getSocket(); return new Promise((resolve) => { // ... }); } getSocket() { if (!this.socket) { this.socket = new WebSocket( `ws://localhost:${CONFIG.PORT}?from=client&id=${clientId}`, 'echo-protocol', ); } return this.socket; } 服務端: // public/javascript/websocket/websocket.server.js connectToServer(httpServer) { initWsServer(httpServer); wsServer.on('request', (request) => { // uri: ws://localhost:8888?from=client&id=xxxx-xxxx-xxxx-xxxx logger.info('[ws server] request'); const connection = request.accept('echo-protocol', request.origin); const queryStrings = querystring.parse(request.resource.replace(/(^\/|\?)/g, '')); // 每有連接連到websocket服務器,就將當前連接保存到map中 setConnectionToMap(connection, queryStrings); connection.on('message', onMessage); connection.on('close', (reasonCode, description) => { logger.info(`[ws server] connection closed ${reasonCode} ${description}`); }); }); wsServer.on('close', (connection, reason, description) => { logger.info('[ws server] some connection disconnect.'); logger.info(reason, description); }); } 然后在瀏覽器端點擊下載的時候,會傳遞兩個主要的字段resourceId(在代碼中由parentId和childId組成)和客戶端生成的bClientId。這兩個id有什么用呢? 每次點擊下載,都會在Web服務器中生成一個WebSocket的客戶端,那么這個resouceId就是作為在服務器中生成的WebSocket服務器的key值。 bClientId主要是為了區分瀏覽器的客戶端,因為考慮到同時可能會有多個瀏覽器接入,這樣在WebSocket服務器中產生消息的時候,就可以用這個id來區分應該發送給哪個瀏覽器客戶端 客戶端: // list.jsx http.get( 'download', { code, filename, parent_id: row.id, child_id: childId, download_url: url, client_id: clientId, }, ); // routes/api.js router.get('/download', async (req, res) => { const { code, filename } = req.query; const url = req.query.download_url; const clientId = req.query.client_id; const parentId = req.query.parent_id; const childId = req.query.child_id; const connectionId = `${parentId}-${childId}`; const params = { code, url, filename, parent_id: parentId, child_id: childId, client_id: clientId, }; const flag = await AnnieDownloader.download(connectionId, params); if (flag) { await res.json({ code: 200 }); } else { await res.json({ code: 500, msg: 'download error' }); } }); // public/javascript/annie.js async download(connectionId, params) { //... // 當annie下載時,會進行數據監聽,這里會用到節流,防止進度回傳太快,websocket服務器無法反應 downloadProcess.stdout.on('data', throttle((chunk) => { try { if (!chunk) { isDownloading = false; } // 這里主要做的是解析數據,然后發送進度和速度等信息給websocket服務器 getDownloadInfo(chunk, ws, params); } catch (e) { downloadSuccess = false; WsClient.close(params.client_id, connectionId, 'download error'); this.stop(connectionId); logger.error(`[server annie download] error: ${e}`); } }, 500, 300)); } 服務端收到進度以及速度的消息后,回傳給客戶端,如果進度達到了100%,那么就刪除掉存在server中的服務器中起的websocket的客戶端,并且發送一個客戶端被關閉的通知,通知瀏覽器已經下載完成。 // public/javascript/websocket/websocket.server.js function onMessage(message) { const data = JSON.parse(message.utf8Data); const id = data.client_id; if (data.event === 'close') { logger.info('[ws server] close event'); closeConnection(id, data); } else { getConnectionAndSendProgressToClient(data, id); } } function getConnectionAndSendProgressToClient(data, clientId) { const browserClient = clientsMap.get(clientId); // logger.info(`[ws server] send ${JSON.stringify(data)} to client ${clientId}`); if (browserClient) { const serverClientId = `${data.parent_id}-${data.child_id}`; const serverClient = clientsMap.get(serverClientId); // 發送從web服務器中傳過來的進度、速度給瀏覽器 browserClient.send(JSON.stringify(data)); // 如果進度已經達到了100% if (data.progress >= 100) { logger.info(`[ws server] file has been download successfully, progress is ${data.progress}`); logger.info(`[ws server] server client ${serverClientId} ready to disconnect`); // 從clientsMap將當前的這個由web服務器創建的websocket客戶端移除 // 然后關閉當前連接 // 同時發送下載完成的消息給瀏覽器 clientsMap.delete(serverClientId); serverClient.send(JSON.stringify({ connectionId: serverClientId, event: 'complete' })); serverClient.close('download completed'); } } } 整體來說就這么多,有一點需要指出,annie在解析的時候有時候可能消息處理不是很穩定,導致我數據解析的時候出現了一些問題,但是我用mock的數據以及mock的進度條回傳是不會出現問題的。 最后總結 多讀書,多看報,少吃零食,多睡覺??????
轉載自://www.cnblogs.com/rynxiao/p/11811353.html
国产无遮挡无码视频在线观看