白嫖小程序云存储空间,手撸你的专属云盘

前言

之前在开发微信小程序的时候,发现官方给每个小程序分配了5g的免费云存储空间和每个月5g的cdn流量(免费版):
白嫖小程序云存储空间,手撸你的专属云盘_第1张图片
在小程序的开发后台可以查看云存储上的文件,文件本质上是存在cdn上的,每个文件都提供了专属的downLoad url,靠着这个url我们就可以下载部署在云端的文件,也就是说上传的文件自带cdn加速。
白嫖小程序云存储空间,手撸你的专属云盘_第2张图片
5G的空间不算少,自己的小程序用不到额外的云存储资源,这个资源拿来给自己搭建一个私有云盘岂不美哉?以后自己的一些小文件就可以放在上面,方便存储和下载。诸位如果没有开发过小程序也没有关系,在微信公众平台上随便申请个工具人小程序,然后开启云开发即可,我们只是白嫖云存储空间。项目地址见文末。

需求分析

要完成我们的设想,我们先罗列下我们需要哪些功能:

  • 文件本地上传到云存储
  • 当前文件列表的展示
  • 已上传文件的下载和删除
  • 简单的登录和api操作鉴权
  • 具有良好的交互,包括进度条等功能

小程序云存储的相关api支持服务器端调用,不支持浏览器直接调用,所以为了操作云存储的相关api,我们需要开启一个中继的node服务作为服务器,顺便管理我们的文件列表。
整个系统的工作流应该是这样的:在我们的前端服务通过用户交互,上传文件到中继的node服务上,node服务器将接收到的文件上传给小程序的云存储空间,获取返回的文件的相关信息(主要是download url),同时在数据库内维护文件列表的相关信息(直接存在小程序对应的数据库中即可)。前端服务会请求后端获取云存储中的文件列表,通过用户的交互可对各个文件进行删除和下载等操作(实际上是向node服务器发送请求,由node服务器调用官方的各种api来对云端的数据进行处理)。
在工具链的选择上,采取react + antd + typescript的技术方案,后端服务使用node + express。

核心功能实现

文件上传

上传逻辑前端部分

首先我们从数据流的源头开始,开始搭建文件核心上传部分index.tsx

import React, {
    useState, useEffect, useReducer } from 'react';
import * as s from './color.css';
import withStyles from 'isomorphic-style-loader/withStyles';
import {
    Layout, Upload, Card, Button, message, Table, Progress, Spin } from 'antd';
import {
    UploadOutlined } from '@ant-design/icons';
import {
    upload } from '@utils/upload';
import {
    UploadFile, UploadChangeParam } from 'antd/lib/upload/interface';
import {
    fileObj, parseList, columns, FileListAction, ProgressObj, ProgressAction } from './accessory';
const {
    Header, Content, Footer } = Layout;
//  省略部分依赖

function ShowComponent() {
   
    //  文件上传列表的hooks
    const [fileList, setFList] = useReducer(listReducer, []);
    //  省略无关代码
    //  ......
    async function handleChange(info: UploadChangeParam<UploadFile<any>>) {
   
        const {
    fileList: newFileList, file } = info;
        //  上传文件的核心逻辑
        const ans = await upload(info);
        const {
    fileData = {
   } } = ans;
        if (fileData.fileName) {
   
            setFList({
    type: 'update', payload: Object.assign(fileData, {
    key: fileData._id }) });
            message.success(`${
     info.file.name} 上传成功。`);
        } else {
   
          message.error(`${
     info.file.name} 上传失败。`);
          return;
        }
      }

    return (
        <Layout className={
   s.layout}>
            <Header>
                <div className={
   s.title}>自己的网盘</div>
            </Header>
            <Content style={
   {
    padding: '50px 50px' }}>
                <div className={
   s.siteLayoutContent}>
                    <Upload
                        customRequest={
   () => {
   }}
                        onChange={
   handleChange}
                        showUploadList={
   false}
                        multiple={
   true}
                    >
                        <Button>
                            <UploadOutlined /> Click to Upload
                        </Button>
                    </Upload>
                </div>
            </Content>
        </Layout>
    )
}
export default withStyles(s)(ShowComponent);

这部分的逻辑很简单,主要是通过react+antd搭建UI,使用antd的Upload控件完成上传文件的相关交互,将获取到的文件对象传递给封装好的upload函数,接下来我们来看看upload.tsx中的逻辑:

import {UploadFile, UploadChangeParam } from 'antd/lib/upload/interface';
import { reqPost, apiMap, request, host } from '@utils/api';
import { ProgressObj, ProgressAction } from '../entry/component/content/accessory';

const SIZE = 1 * 1024 * 1024; // 切片大小

// 生成文件切片
function createFileChunk(file: File | Blob | undefined, size = SIZE) {
    if (!file) {
        return [];
    }
    const fileChunkList = [];
    let cur = 0;
    while (cur < file.size) {
        //  对字节码进行切割
        fileChunkList.push({ file: file.slice(cur, cur + size) });
        cur += size;
    }
    return fileChunkList;
}

interface FileObj extends File {
    name: string;
}

//  发送单个的文件切片
export async function uploadFile(params: FormData, fileName: string) {
    return request(host + apiMap.UPLOAD_FILE_SLICE, {
        method: 'post',
        data: params,
    });
}

//  给服务器发送合并切片的逻辑
export async function fileMergeReq(name: string, fileSize: number) {
    return reqPost(apiMap.MERGE_SLICE, { fileName: name, size: SIZE, fileSize: fileSize });
}

export async function upload(info: UploadChangeParam>) {
    //  获取切片的文件列表
    const fileList = createFileChunk(info.file.originFileObj);
    if (!info.file.originFileObj) {
        return '';
    }
    const { name: filename, size: fileSize } = info.file.originFileObj as FileObj;
    //  生成数据包list
    const dataPkg = fileList.map(({ file }, index) => ({
        chunk: file,
        hash: `${filename}-${index}` // 文件名 + 数组下标
        }));
    //  通过formdata依次发送数据包
    const uploadReqList = dataPkg.map(({ chunk, hash}) => {
        const formData = new FormData();
        formData.append('chunk', chunk);
        formData.append('hash', hash);
        formData.append('filename', filename);
        return formData
    });
    const promiseArr = uploadReqList.map(item => uploadFile(item, filename));
    await Promise.all(promiseArr);
    //  全部发送完成后发送合并切片的请求
    const ans = await fileMergeReq(filename, fileSize);
    callBack({ type: 'delete', fileName: filename });
    return ans;
}

这里的逻辑并不复杂,核心是思想是将用户上传的文件切成每个1M的文件切片,并做好标记,将所有的文件切片送到服务器,服务器接收到所有的切片后告知前端接收完成,前端发送合并请求,告知服务器可以将所有的文件切片依据做好的标记合并成原文件。

上传逻辑server端部分

接下来我们看看服务器端与之配合的代码:

let ownTool = require('xiaohuli-package');
let fs = require('fs');
const request = require

你可能感兴趣的:(长文,javascript,node.js,前端)