最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Vue3利用組合式函數(shù)和Shared Worker實(shí)現(xiàn)后臺(tái)分片上傳

 更新時(shí)間:2023年10月27日 07:46:37   作者:Aoba_xu  
這篇文章主要為大家詳細(xì)介紹了Vue3如何利用組合式函數(shù)和Shared Worker實(shí)現(xiàn)后臺(tái)分片上傳(帶哈希計(jì)算),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

1.背景

最近項(xiàng)目需求里有個(gè)文件上傳功能,而客戶需求里的文件基本上是比較大的,基本上得有 1 GiB 以上的大小,而上傳大文件尤其是讀大文件,可能會(huì)造成卡 UI 或者說(shuō)點(diǎn)不動(dòng)的問(wèn)題。而用后臺(tái)的 Worker 去實(shí)現(xiàn)是一個(gè)比較不錯(cuò)的解決辦法。

2.原理講解

2.1Shared Worker

Shared Worker 的好處是可以從幾個(gè)瀏覽上下文中訪問(wèn),例如幾個(gè)窗口、iframe 或其他 worker。這樣我們可以保證全局的頁(yè)面上傳任務(wù)都在我們的控制之下,甚至可以防止重復(fù)提交等功能。

2.2組合式函數(shù)

組合式函數(shù)的好處是在 Vue 3 是可以在任何 *.vue 文件中使用,并且是響應(yīng)式方法,可以偵聽(tīng) pinia 內(nèi) token 等的變化,傳遞給 Worker

2.3簡(jiǎn)單流程設(shè)計(jì)

3.代碼

upload-worker.ts 代碼

import { sha256 } from '@noble/hashes/sha256';
import { bytesToHex as toHex } from '@noble/hashes/utils';
interface SharedWorkerGlobalScope {
  onconnect: (event: MessageEvent<any>) => void;
}
const _self: SharedWorkerGlobalScope = self as any;
/**
 * 分片大小
 */
const pieceSize = 1024 * 1024;
/**
 * 消息參數(shù)
 */
interface MessageArg<T> {
  /**
   * 函數(shù)名
   */
  func: string;
  /**
   * 參數(shù)
   */
  arg: T;
}
/**
 * 上傳任務(wù)信息
 */
interface UploadTaskInfo {
  /**
   * 文件名
   */
  fileName: string;
  /**
   * 上傳路徑
   */
  uploadPath: string;
  /**
   * 任務(wù) id
   */
  id: string;
  /**
   * 文件大小
   */
  size: number;
  /**
   * 上傳進(jìn)度
   */
  progress: number;
  /**
   * 上傳速度
   */
  speed?: number;
  /**
   * 任務(wù)狀態(tài)
   */
  status: 'uploading' | 'paused' | 'canceled' | 'done' | 'error' | 'waiting';
  /**
   * 開(kāi)始時(shí)間
   */
  startTime?: Date;
  /**
   * 結(jié)束時(shí)間
   */
  endTime?: Date;
  /**
   * 錯(cuò)誤信息
   */
  errorMessage?: string;
}
/**
 * 上傳任務(wù)
 */
interface UploadTask extends UploadTaskInfo {
  file: File;
  pieces: Array<boolean>;
  abort?: AbortController;
}
/**
 * 任務(wù)/哈希值映射
 */
const hashs = new Map();
/**
 * 上傳任務(wù)列表
 */
const uploadTasks: Array<UploadTask> = [];
/**
 * 狀態(tài)接收器
 */
const statusReceivers = new Map<string, MessagePort>();
/**
 * token 倉(cāng)庫(kù)
 */
const tokenStore = {
  /**
   * token
   */
  BearerToken: '',
};
/**
 * 返回上傳狀態(tài)
 * @param task 上傳任務(wù)
 */
const updateStatus = (task: UploadTaskInfo) => {
  const taskInfo: UploadTaskInfo = {
    fileName: task.fileName,
    uploadPath: task.uploadPath,
    id: task.id,
    size: task.size,
    progress: task.progress,
    speed: task.speed,
    status: task.status,
    startTime: task.startTime,
    endTime: task.endTime,
    errorMessage: task.errorMessage,
  };
  statusReceivers.forEach((item) => {
    item.postMessage(taskInfo);
  });
};
/**
 * 運(yùn)行上傳任務(wù)
 * @param task 上傳任務(wù)
 */
const runUpload = async (task: UploadTask) => {
  task.status = 'uploading';
  const hash = hashs.get(task.id) || sha256.create();
  hashs.set(task.id, hash);
  let retryCount = 0;
  const abort = new AbortController();
  task.abort = abort;
  while (task.status === 'uploading') {
    const startTime = Date.now();
    const index = task.pieces.findIndex((item) => !item);
    if (index === -1) {
      try {
        const response: { code: number; message: string } = await fetch(
          '/api/File/Upload',
          {
            method: 'PUT',
            headers: {
              Authorization: tokenStore.BearerToken,
              'Content-Type': 'application/json',
            },
            body: JSON.stringify({
              id: task.id,
              fileHash: toHex(hash.digest()),
              filePath: task.uploadPath,
            }),
          }
        ).then((res) => res.json());
        if (response.code !== 200) {
          throw new Error(response.message);
        }
        task.status = 'done';
        task.endTime = new Date();
        updateStatus(task);
      } catch (e: any) {
        task.status = 'error';
        task.errorMessage = e.toString();
        task.endTime = new Date();
        deleteUpload(task.id);
        updateStatus(task);
      }
      break;
    }
    const start = index * pieceSize;
    const end = start + pieceSize >= task.size ? task.size : start + pieceSize;
    const buffer = task.file.slice(index * pieceSize, end);
    hash.update(new Uint8Array(await buffer.arrayBuffer()));
    const form = new FormData();
    form.append('file', buffer);
    let isTimeout = false;
    try {
      const timer = setTimeout(() => {
        isTimeout = true;
        abort.abort();
      }, 8000);
      const response: { code: number; message: string } = await fetch(
        `/api/File/Upload?id=${task.id}&offset=${start}`,
        {
          method: 'POST',
          body: form,
          headers: {
            Authorization: tokenStore.BearerToken,
          },
          signal: abort.signal,
        }
      ).then((res) => res.json());
      clearTimeout(timer);
      if (response.code !== 200) {
        throw new Error(response.message);
      }
      task.pieces[index] = true;
      task.progress =
        task.pieces.filter((item) => item).length / task.pieces.length;
      task.speed = (pieceSize / (Date.now() - startTime)) * 1000;
      updateStatus(task);
    } catch (e: any) {
      retryCount++;
      if (retryCount > 3) {
        task.status = 'error';
        if (isTimeout) {
          task.errorMessage = 'UploadTimeout';
        } else {
          task.errorMessage = e.toString();
        }
        task.endTime = new Date();
        deleteUpload(task.id);
        updateStatus(task);
      }
    }
    runNextUpload();
  }
};
/**
 * 運(yùn)行下一個(gè)上傳任務(wù)
 */
const runNextUpload = async () => {
  if (uploadTasks.filter((item) => item.status === 'uploading').length > 3) {
    return;
  }
  const task = uploadTasks.find((item) => item.status === 'waiting');
  if (task) {
    await runUpload(task);
  }
};
/**
 * 排隊(duì)上傳
 * @param e 消息事件
 */
const queueUpload = async (
  e: MessageEvent<
    MessageArg<{
      id: string;
      file: File;
      uploadPath: string;
    }>
  >
) => {
  uploadTasks.push({
    file: e.data.arg.file,
    fileName: e.data.arg.file.name,
    id: e.data.arg.id,
    uploadPath: e.data.arg.uploadPath,
    size: e.data.arg.file.size,
    progress: 0,
    speed: 0,
    status: 'waiting',
    pieces: new Array(Math.ceil(e.data.arg.file.size / pieceSize)).fill(false),
    errorMessage: undefined,
  });
  updateStatus(uploadTasks[uploadTasks.length - 1]);
  await runNextUpload();
};
/**
 * 注冊(cè)狀態(tài)接收器
 * @param e 消息事件
 * @param sender 發(fā)送者
 */
const registerStatusReceiver = (
  e: MessageEvent<MessageArg<string>>,
  sender?: MessagePort
) => {
  if (sender) statusReceivers.set(e.data.arg, sender);
};
/**
 * 注銷狀態(tài)接收器
 * @param e 消息事件
 */
const unregisterStatusReceiver = (e: MessageEvent<MessageArg<string>>) => {
  statusReceivers.delete(e.data.arg);
};
/**
 * 更新 token
 * @param e 消息事件
 */
const updateToken = (e: MessageEvent<MessageArg<string>>) => {
  tokenStore.BearerToken = 'Bearer ' + e.data.arg;
};
/**
 * 暫停上傳
 * @param e 消息事件
 */
const pauseUpload = (e: MessageEvent<MessageArg<string>>) => {
  const task = uploadTasks.find((item) => item.id === e.data.arg);
  if (task) {
    task.status = 'paused';
    if (task.abort) {
      task.abort.abort();
    }
    updateStatus(task);
  }
};
/**
 * 取消上傳
 * @param e 消息事件
 */
const cancelUpload = (e: MessageEvent<MessageArg<string>>) => {
  const task = uploadTasks.find((item) => item.id === e.data.arg);
  if (task) {
    task.status = 'canceled';
    if (task.abort) {
      task.abort.abort();
    }
    deleteUpload(task.id);
    updateStatus(task);
  }
};
/**
 * 刪除上傳
 * @param id 任務(wù) id
 */
const deleteUpload = async (id: string) => {
  uploadTasks.splice(
    uploadTasks.findIndex((item) => item.id === id),
    1
  );
  hashs.delete(id);
  await fetch(`/api/File/Upload?id=${id}`, {
    method: 'DELETE',
    headers: {
      Authorization: tokenStore.BearerToken,
    },
  }).then((res) => res.json());
};
/**
 * 消息路由
 */
const messageRoute = new Map<
  string,
  (e: MessageEvent<MessageArg<any>>, sender?: MessagePort) => void
>([
  ['queueUpload', queueUpload],
  ['registerStatusReceiver', registerStatusReceiver],
  ['updateToken', updateToken],
  ['pauseUpload', pauseUpload],
  ['cancelUpload', cancelUpload],
  ['unregisterStatusReceiver', unregisterStatusReceiver],
]);
// 監(jiān)聽(tīng)連接
_self.onconnect = (e) => {
  const port = e.ports[0];
  port.onmessage = async (e) => {
    // 調(diào)用函數(shù)
    const func = messageRoute.get(e.data.func);
    if (func) {
      func(e, port);
    }
  };
  port.start();
};

upload-service.ts 代碼

import UploadWorker from './upload-worker?sharedworker';
import { onUnmounted, ref, watch } from 'vue';
import { storeToRefs } from 'pinia';
import { useAuthStore } from 'src/stores/auth';
/**
 * 上傳任務(wù)信息
 */
interface UploadTaskInfo {
  /**
   * 文件名
   */
  fileName: string;
  /**
   * 上傳路徑
   */
  uploadPath: string;
  /**
   * 任務(wù) id
   */
  id: string;
  /**
   * 文件大小
   */
  size: number;
  /**
   * 上傳進(jìn)度
   */
  progress: number;
  /**
   * 上傳速度
   */
  speed?: number;
  /**
   * 任務(wù)狀態(tài)
   */
  status: 'uploading' | 'paused' | 'canceled' | 'done' | 'error' | 'waiting';
  /**
   * 開(kāi)始時(shí)間
   */
  startTime?: Date;
  /**
   * 結(jié)束時(shí)間
   */
  endTime?: Date;
  /**
   * 錯(cuò)誤信息
   */
  errorMessage?: string;
}
/**
 * 上傳服務(wù)
 */
export const useUploadService = () => {
  const store = storeToRefs(useAuthStore());
  // 創(chuàng)建共享 worker
  const worker = new UploadWorker();
  /**
   * 上傳任務(wù)列表
   */
  const uploadTasks = ref<Map<string, UploadTaskInfo>>(
    new Map<string, UploadTaskInfo>()
  );
  // 是否已注冊(cè)狀態(tài)接收器
  const isRegistered = ref(false);
  // 服務(wù) id
  const serviceId = crypto.randomUUID();
  // 監(jiān)聽(tīng)上傳任務(wù)列表變化(只有在注冊(cè)狀態(tài)接收器后才會(huì)收到消息)
  worker.port.onmessage = (e: MessageEvent<UploadTaskInfo>) => {
    uploadTasks.value.set(e.data.id, e.data);
  };
  // 更新 token
  worker.port.postMessage({
    func: 'updateToken',
    arg: store.token.value,
  });
  watch(store.token, (token) => {
    worker.port.postMessage({
      func: 'updateToken',
      arg: token,
    });
  });
  /**
   * 排隊(duì)上傳
   * @param file 文件
   * @param uploadPath 上傳路徑
   */
  const queueUpload = (file: File, uploadPath: string) => {
    worker.port.postMessage({
      func: 'queueUpload',
      arg: {
        id: crypto.randomUUID(),
        file: file,
        uploadPath: uploadPath,
      },
    });
  };
  /**
   * 暫停上傳
   * @param id 任務(wù) id
   */
  const pauseUpload = (id: string) => {
    worker.port.postMessage({
      func: 'pauseUpload',
      arg: id,
    });
  };
  /**
   * 取消上傳
   * @param id 任務(wù) id
   */
  const cancelUpload = (id: string) => {
    worker.port.postMessage({
      func: 'cancelUpload',
      arg: id,
    });
  };
  /**
   * 注冊(cè)狀態(tài)接收器
   */
  const registerStatusReceiver = () => {
    worker.port.postMessage({
      func: 'registerStatusReceiver',
      arg: serviceId,
    });
    isRegistered.value = true;
  };
  /**
   * 注銷狀態(tài)接收器
   */
  const unregisterStatusReceiver = () => {
    worker.port.postMessage({
      func: 'unregisterStatusReceiver',
      arg: serviceId,
    });
    isRegistered.value = false;
  };
  onUnmounted(() => {
    unregisterStatusReceiver();
    worker.port.close();
  });
  return {
    uploadTasks,
    queueUpload,
    pauseUpload,
    cancelUpload,
    registerStatusReceiver,
    unregisterStatusReceiver,
  };
};

4.用法

// 引入組合式函數(shù)
const uploadService = useUploadService();
// 注冊(cè)狀態(tài)接收器
uploadService.registerStatusReceiver();
// 表單綁定上傳方法
const upload = (file: File, filePath: string) => {
  uploadService.queueUpload(file, filePath);
}
// 監(jiān)聽(tīng)上傳進(jìn)度,當(dāng)然也可以直接展示在界面,畢竟是 Ref
watch(uploadService.uploadTasks, console.log);

以上就是Vue3利用組合式函數(shù)和Shared Worker實(shí)現(xiàn)后臺(tái)分片上傳的詳細(xì)內(nèi)容,更多關(guān)于Vue3分片上傳的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • vue element 表頭添加斜線的實(shí)現(xiàn)代碼

    vue element 表頭添加斜線的實(shí)現(xiàn)代碼

    這篇文章主要介紹了vue element 表頭添加斜線的實(shí)現(xiàn)代碼,代碼簡(jiǎn)單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-11-11
  • vue插件draggable實(shí)現(xiàn)拖拽移動(dòng)圖片順序

    vue插件draggable實(shí)現(xiàn)拖拽移動(dòng)圖片順序

    這篇文章主要為大家詳細(xì)介紹了vue插件draggable實(shí)現(xiàn)拖拽移動(dòng)圖片順序,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-12-12
  • 詳解Vue2.x-directive的學(xué)習(xí)筆記

    詳解Vue2.x-directive的學(xué)習(xí)筆記

    這篇文章主要介紹了詳解Vue2.x-directive的學(xué)習(xí)筆記,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-07-07
  • vue移動(dòng)端使用appClound拉起支付寶支付的實(shí)現(xiàn)方法

    vue移動(dòng)端使用appClound拉起支付寶支付的實(shí)現(xiàn)方法

    這篇文章主要介紹了vue移動(dòng)端使用appClound拉起支付寶支付的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • vue 中的 render 函數(shù)作用詳解

    vue 中的 render 函數(shù)作用詳解

    這篇文章主要介紹了vue 中的 render 函數(shù)作用,通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-02-02
  • vue封裝jquery修改自身及兄弟元素的方法

    vue封裝jquery修改自身及兄弟元素的方法

    本文主要介紹了vue封裝jquery修改自身及兄弟元素的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07
  • 前端報(bào)錯(cuò)npm ERR! cb() never called!問(wèn)題解決辦法

    前端報(bào)錯(cuò)npm ERR! cb() never called!問(wèn)題解決辦法

    最近接手了一個(gè)前臺(tái)項(xiàng)目,執(zhí)行npm install的時(shí)候一直報(bào)錯(cuò),所以這里就給大家總結(jié)下,這篇文章主要給給大家介紹了關(guān)于前端報(bào)錯(cuò)npm?ERR! cb() never called!問(wèn)題的解決辦法,需要的朋友可以參考下
    2024-05-05
  • vue 使用ref 讓父組件調(diào)用子組件的方法

    vue 使用ref 讓父組件調(diào)用子組件的方法

    這篇文章主要介紹了vue 使用ref 讓父組件調(diào)用子組件的方法,需要的朋友可以參考下
    2018-02-02
  • vue 點(diǎn)擊按鈕增加一行的方法

    vue 點(diǎn)擊按鈕增加一行的方法

    今天小編就為大家分享一篇vue 點(diǎn)擊按鈕增加一行的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-09-09
  • Vue3 如何使用CryptoJS加密

    Vue3 如何使用CryptoJS加密

    CryptoJS是一個(gè)強(qiáng)大的JavaScript庫(kù),它提供了多種加密解密功能,尤其是AES加密方法,使用CryptoJS,我們可以有效地保護(hù)數(shù)據(jù)安全,防止信息泄露,通過(guò)簡(jiǎn)單的安裝和函數(shù)編寫(xiě),本文給大家介紹Vue3 如何使用CryptoJS加密,感興趣的朋友一起看看吧
    2024-10-10

最新評(píng)論

乌审旗| 图木舒克市| 玛纳斯县| 雷州市| 健康| 宜川县| 酒泉市| 隆子县| 宣恩县| 万全县| 昌乐县| 阜平县| 许昌市| 湟中县| 余干县| 大方县| 克东县| 苍山县| 西充县| 韩城市| 贺兰县| 武夷山市| 县级市| 马边| 荃湾区| 阜平县| 乌鲁木齐市| 东丰县| 乌鲁木齐县| 巴林右旗| 大城县| 南投市| 甘孜县| 泌阳县| 浮山县| 萨迦县| 诸城市| 东港市| 八宿县| 长汀县| 牡丹江市|