node文件資源管理器的解壓縮從零實現
解壓縮
這里使用較為常用的 7z 來處理壓縮包,它可以解開常見的壓縮包格式
Unpacking only: APFS, AR, ARJ, CAB, CHM, CPIO, CramFS, DMG, EXT, FAT, GPT, HFS, IHEX, ISO, LZH, LZMA, MBR, MSI, NSIS, NTFS, QCOW2, RAR, RPM, SquashFS, UDF, UEFI, VDI, VHD, VHDX, VMDK, XAR and Z.
開發(fā)
預下載 mac 與 linux 版本的 7z 二進制文件,放置于 explorer-manage/src/7zip/linux 與 /mac 目錄內。可前往 7z 官方進行下載,下載鏈接。
也可以使用 7zip-bin 這個依賴,內部包含所有環(huán)境可運行的二進制文件。由于項目是由鏡像進行運行,使用全環(huán)境的包會加大鏡像的體積。
所以這里單獨下載特定環(huán)境的下二進制文件,可能版本會比較舊,最近更新為 2022/5/16 。目前最新的 2023/06/20@23.01 版本。
使用 node-7z 這個依賴處理 7z 的輸入輸出
安裝依賴
pnpm i node-7z
運行文件
// https://laysent.com/til/2019-12-02_7zip-bin-in-alpine-docker // https://www.npmjs.com/package/node-7z // https://www.7-zip.org/download.html // import sevenBin from '7zip-bin' import node7z from 'node-7z' import { parseFilePath } from './parse-path.mjs' import path from 'path' import { dirname } from 'node:path' import { fileURLToPath } from 'node:url' import { formatPath } from '../../lib/format-path.mjs' const __dirname = dirname(fileURLToPath(import.meta.url)) /** * @type {import('node-7z').SevenZipOptions} */ const base_option = { $bin: process.platform === 'darwin' ? path.join(__dirname, './mac/7zz') : path.join(__dirname, './linux/7zzs'), recursive: true, exclude: ['!__MACOSX/*', '!.DS_store'], latestTimeStamp: false, } /** * @param path {string} * @param out_path {string|undefined} * @param pwd {string | number | undefined} * @returns {import('node-7z').ZipStream} */ export const node7zaUnpackAction = (path, out_path = '', pwd = 'pwd') => { const join_path = formatPath(path) const { file_dir_path } = parseFilePath(join_path) return node7z.extractFull(join_path, formatPath(out_path) || `${file_dir_path}/`, { ...base_option, password: pwd, }) } /** * @param path {string} * @param pwd {string | number | undefined} * @returns {import('node-7z').ZipStream} */ export const node7zListAction = (path, pwd = 'pwd') => { const join_path = formatPath(path) return node7z.list(join_path, { ...base_option, password: pwd }) }
簡單封裝下 node7zaUnpackAction 與 node7zListAction 方法
- node7zaUnpackAction:解壓縮方法
- node7zListAction:查看當前壓縮包內容
explorer 客戶端展示
大致設計為彈窗模式,提供一個解壓縮位置,默認當前壓縮包位置。再提供一個密碼輸入欄,用于帶密碼的壓縮包解壓。
解壓縮一個超大包時,可能會超過 http 的請求超時時間,瀏覽器會主動關閉這次請求。導致壓縮包沒有解壓縮完畢,請求就已經關閉了。雖然 node 還在后臺進行解壓縮。但是客戶端無法知道是否解壓縮完畢。
可通過延長 http 的請求超時時間。也可使用 stream 逐步輸出內容的方式避免超時,客戶端部分可以實時看到當前解壓縮的進度。類似像 AI 機器人提問時,文字逐字出現的效果。
查看壓縮包內容
直接使用 server action 調用 node7zListAction 方法即可
解壓縮
使用 node-7z 的輸出流逐步輸出到瀏覽器
封裝一個 post api 接口。
- 監(jiān)聽 node-7z 返回的數據流
.on('data')
事件。 - 對數據流做
encoder.encode(JSON.stringify(value) + ‘, ’)
格式化操作。方便客戶端讀取數據流。 - 每秒往客戶端輸出一個時間戳避免請求超時
stream.push({ loading: Date.now() })
- 10 分鐘后關閉 2 的定時輸出,讓其自然超時。
- 客戶端通過 fetch 獲取數據流,具體可以看 unpack 方法
接口 api
import { NextRequest, NextResponse } from 'next/server' import { node7zaUnpackAction } from '@/explorer-manager/src/7zip/7zip.mjs' import { nodeStreamToIterator } from '@/explorer-manager/src/main.mjs' const encoder = new TextEncoder() const iteratorToStream = (iterator: AsyncGenerator) => { return new ReadableStream({ async pull(controller) { const { value, done } = await iterator.next() if (done) { controller.close() } else { controller.enqueue(encoder.encode(JSON.stringify(value) + ', ')) } }, }) } export const POST = async (req: NextRequest) => { const { path, out_path, pwd } = await req.json() try { const stream = node7zaUnpackAction(path, out_path, pwd) stream.on('data', (item) => { console.log('data', item.file) }) const interval = setInterval(() => { console.log('interval', stream.info) stream.push({ loading: Date.now() }) }, 1000) const timeout = setTimeout( () => { clearInterval(interval) }, 60 * 10 * 1000, ) stream.on('end', () => { console.log('end', stream.info) stream.push({ done: JSON.stringify(Object.fromEntries(stream.info), null, 2), }) clearTimeout(timeout) clearInterval(interval) stream.push(null) }) return new NextResponse(iteratorToStream(nodeStreamToIterator(stream)), { headers: { 'Content-Type': 'application/octet-stream', }, }) } catch (e) { return NextResponse.json({ ret: -1, err_msg: e }) } }
客戶端彈窗組件
'use client' import React, { useState } from 'react' import { Card, Modal, Space, Table } from 'antd' import UnpackForm from '@/components/unpack-modal/unpack-form' import { isEmpty } from 'lodash' import { useRequest } from 'ahooks' import Bit from '@/components/bit' import DateFormat from '@/components/date-format' import { UnpackItemType } from '@/explorer-manager/src/7zip/types' import { useUnpackPathDispatch, useUnpackPathStore } from '@/components/unpack-modal/unpack-path-context' import { useUpdateReaddirList } from '@/app/path/readdir-context' import { unpackListAction } from '@/components/unpack-modal/action' let pack_list_path = '' const UnpackModal: React.FC = () => { const unpack_path = useUnpackPathStore() const changeUnpackPath = useUnpackPathDispatch() const [unpack_list, changeUnpackList] = useState<UnpackItemType['list']>([]) const { update } = useUpdateReaddirList() const packList = useRequest( async (form_val) => { pack_list_path = unpack_path const { pwd } = await form_val return unpackListAction(unpack_path, pwd) }, { manual: true, }, ) const unpack = useRequest( async (form_val) => { pack_list_path = unpack_path unpack_list.length = 0 const { out_path, pwd } = await form_val const res = await fetch('/path/api/unpack', { method: 'post', body: JSON.stringify({ path: unpack_path, out_path, pwd: pwd }), }) if (res.body) { const reader = res.body.getReader() const decode = new TextDecoder() while (1) { const { done, value } = await reader.read() const decode_value = decode .decode(value) .split(', ') .filter((text) => Boolean(String(text).trim())) .map((value) => { try { return value ? JSON.parse(value) : { value } } catch (e) { return { value } } }) .filter((item) => !item.loading) .reverse() !isEmpty(decode_value) && changeUnpackList((unpack_list) => decode_value.concat(unpack_list)) if (done) { break } } } return Promise.resolve().then(update) }, { manual: true, }, ) return ( <Modal title="解壓縮" open={!isEmpty(unpack_path)} width={1000} onCancel={() => changeUnpackPath('')} footer={false} destroyOnClose={true} > <UnpackForm packList={packList} unpack={unpack} /> <Space direction="vertical" style={{ width: '100%' }}> {pack_list_path === unpack_path && !isEmpty(unpack_list) && ( <Card title="unpack" bodyStyle={{ maxHeight: '300px', overflowY: 'scroll', paddingTop: 20, overscrollBehavior: 'contain', }} > {unpack_list.map(({ file, done }) => ( <pre key={file || done}>{file || done}</pre> ))} </Card> )} {pack_list_path === unpack_path && !isEmpty(packList.data) && ( <Card title="壓縮包內容"> {!isEmpty(packList.data?.data) && ( <Table scroll={{ x: true }} rowKey={({ file }) => file} columns={[ { key: 'file', dataIndex: 'file', title: 'file' }, { key: 'size', dataIndex: 'size', title: 'size', width: 100, render: (size) => { return <Bit>{size}</Bit> }, }, { key: 'sizeCompressed', dataIndex: 'sizeCompressed', title: 'sizeCompressed', width: 150, render: (size) => { return <Bit>{size}</Bit> }, }, { key: 'datetime', dataIndex: 'datetime', title: 'datetime', width: 180, render: (date) => <DateFormat>{new Date(date).getTime()}</DateFormat>, }, ]} dataSource={packList.data?.data} /> )} {packList.data?.message && <p>{packList.data?.message}</p>} </Card> )} </Space> </Modal> ) } export default UnpackModal
測試用逐字輸出
每秒往客戶端輸出當前時間。持續(xù) 10 分鐘。
import { iteratorToStream, nodeStreamToIterator } from '@/explorer-manager/src/main.mjs' function sleep(time: number) { return new Promise((resolve) => { setTimeout(resolve, time) }) } const encoder = new TextEncoder() async function* makeIterator() { let length = 0 while (length > 60 * 10) { await sleep(1000) yield encoder.encode(`<p>${length} ${new Date().toLocaleString()}</p>`) length += 1 } } export async function POST() { return new Response(iteratorToStream(nodeStreamToIterator(makeIterator())), { headers: { 'Content-Type': 'application/octet-stream' }, }) } export async function GET() { return new Response(iteratorToStream(nodeStreamToIterator(makeIterator())), { headers: { 'Content-Type': 'html' }, }) }
效果
git-repo
以上就是node文件資源管理器的解壓縮從零實現的詳細內容,更多關于node文件資源解壓縮的資料請關注腳本之家其它相關文章!
相關文章
從零開始學習Node.js系列教程之SQLite3和MongoDB用法分析
這篇文章主要介紹了Node.js SQLite3和MongoDB用法,結合實例形式分析了SQLite3和MongoDB數據庫的初始化、連接、查詢等操作的實現技巧與相關注意事項,需要的朋友可以參考下2017-04-04HTTP JSON接口模擬工具Interfake快速入門教程
這篇文章主要為大家介紹了HTTP JSON接口模擬工具Interfake快速入門教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-06-06