electron + react开发一个简易的markdown云盘——文件和文件夹渲染及基本事件

455 阅读4分钟

前言

上一节,已经初始化了整个项目。这一节我直接跳过react-reduxreact-router-dom的配置等等,啥也不说了直接上代码

OSS小文件存储

  • 新建OSS controller
const OSS = window.require('ali-oss');
const fs = window.require('fs');
// ali-oss sdk的插件,简化删除文件和文件夹等的操作
const ExtraOSS = window.require('ali-oss-extra');
// 一个普通的统一消息的类
const {SuccessModel, ErrorModel, FailedModel} = require('./Model');
const Store = window.require('electron-store');
import axios from 'axios';

const settingsStore = new Store({
    name: 'Settings'
});
// 读取配置(配置在main.js设置,可读取环境变量)
const endpoint = settingsStore.get('endpoint');
const access = settingsStore.get('accessKey');
const secret = settingsStore.get('secretKey');
const bucket = settingsStore.get('bucketName');

class AliOSS {
    constructor({accessKeyId, accessKeySecret, bucket, endpoint}) {
        this.ak = accessKeyId;
        this.sk = accessKeySecret;
        this.bucket = bucket;
        this.endPoint = endpoint;
        this.store = OSS({endpoint, accessKeyId, accessKeySecret, bucket});
        this.client = new OSS({endpoint, accessKeyId, accessKeySecret, bucket});
    }

    /**
     * 获取文件
     * @param prefix 文件夹前缀
     * @param delimiter 是否包含子文件夹的读取
     * @return {Promise<*>}
     */
    async getObjectsFromOss(prefix, delimiter) {
        if (delimiter) {
            return await this.client.list({prefix, delimiter})
        } else {
            return await this.client.list({prefix})
        }
    }

    /**
     * 上传文件
     * @param key 云空间的名字
     * @param localPath 本地文件路径
     * @param options
     */
    async uploadFile(key, localPath, options = {type: '.md'}) {
        // object表示上传到OSS的Object名称,localPath表示本地文件或者文件路径
        let {res} = await this.client.put(`${key}${options.type}`, localPath);
        if (res.status === 200) {
            return new SuccessModel({
                msg: '上传成功'
            })
        } else {
            return new FailedModel({
                msg: '上传失败'
            })
        }
    }
    /**
     * 上传文件
     * @param key 云空间的名字
     * @param buffer 文件内容的buffer
     * @param options
     */
    async uploadFile_v2(key, buffer, options = {type: '.md'}) {
        // object表示上传到OSS的Object名称,localPath表示本地文件或者文件路径
        let {res} = await this.client.putStream(`${key}${options.type}`, buffer);
        console.log(buffer)
        if (res.status === 200) {
            return new SuccessModel({
                msg: '上传成功'
            })
        } else {
            return new FailedModel({
                msg: '上传失败'
            })
        }
    }

    /**
     * 判断文件是否存在于云端
     * @param key 文件名
     * @return {Promise<*>}
     */
    async isExistObject(key) {
        try {
            let {res} = await this.client.getObjectMeta(key);
            return new SuccessModel({
                msg: '文件存在于云端',
                data: {
                    lastModified: +new Date(res.headers['last-modified'])
                }
            })
        } catch (e) {
            return new FailedModel({
                msg: '文件不存在于云端'
            })
        }
    }
    
    async downloadFile_v2(key, filePath) {
        try {
            let {res, stream} = await this.client.getStream(key);
            if (res.status === 200) {
                let writeStream = fs.createWriteStream(filePath);
                stream.pipe(writeStream);
                return new SuccessModel({
                    msg: '下载成功'
                });
            } else {
                return new FailedModel({
                    msg: '下载失败'
                })
            }
        } catch (e) {
            return new ErrorModel({
                msg: '阿里云OSS服务器异常'
            })
        }
    }
    /**
     * 取得img的路径
     * @param key 文件名
     * @return {Promise<*>}
     */
    async getImgUrl(key) {
        try {
            return this.client.generateObjectUrl(key);
        } catch (e) {

        }
    }
    /**
     * 创建文件夹,以/结尾
     * @param key 文件名
     * @return {Promise<*>}
     */
    async createDir(key) {
        let url = 'https://' + this.bucket + '.' + this.endPoint + '/' + key;
        return axios.put(url)
    }
    /**
     * 复制文件
     * @param from 
     * @param to
     * @return {Promise<*>}
     */
    async copyFile(from, to) {
        let url = 'https://' + this.bucket + '.' + this.endPoint + '/' + to;
        return axios(url, {
            method: 'put',
            headers: {
                'x-oss-copy-source': '/cloud-doc-editor/' + from
            }
        })
    }
}

const manager = new AliOSS({
    accessKeyId: access,
    endpoint: endpoint,
    accessKeySecret: secret,
    bucket: bucket,
});

const extraManager = new OSS.default({
    accessKeyId: access,
    endpoint: endpoint,
    accessKeySecret: secret,
    bucket: bucket,
});

module.exports = {manager, extraManager};

获取当前路由下的文件夹

    // 引入querystring包处理路由
    const querystring = window.require('querystring');
    ....
    // 得到searchObj:{prefix: xxx}
    const searchObj = querystring.parse(location.search.slice(1));
    // 将返回的数据形成固定的结构
    const filterFile = (prefix, data) => {
        // 文件夹
        let filteredDirs = data.prefixes && data.prefixes.map(dir => {
            // 等到当前的路径的文件夹名和文件夹,并且设置其为不是新文件
            return {dirname: dir.slice(prefix.length, -1), dir: dir.replace(/\/$/g, ''), id: dir, isNew: false}
        });
        
        // 文件
        let filteredObjects = data.objects && data.objects.map(object => {
            let {lastModified, url, name, size} = object;
            let extname = nodePath.extname(name);
            if (extname !== '') {
                let filename = name.slice(prefix.length);
                return {
                    filename,
                    id: name,
                    serverUpdatedAt: +new Date(lastModified),
                    url,
                    extname: extname.slice(extname.indexOf('.') + 1),
                    size
                }
            }
        }).filter(item => item);
        return {
            // 返回的是以id为键名的obj
            dirs: filteredDirs ? array2Obj(filteredDirs) : {},
            objects: filteredObjects ? array2Obj(filteredObjects) : {}
        }
    };
    // 获取当前路径下的文件和文件夹
    const getData = useCallback(() => {
        // 登录之后才能渲染文件夹和文件
        if (loginInfo && loginInfo.user) {
            // 默认的根文件夹是用户id为前缀
            let prefix = searchObj.prefix + '/';
            // 获取当前目录的文件夹和文件
            manager.getObjectsFromOss(prefix, '/')
                .then(data => {
                    // 去掉前缀
                    let {objects, dirs} = filterFile(prefix, data);
                    // 设置redux store的中的数据
                    setCloudObjects(objects || {});
                    setCloudDir(dirs || {});
                });
        }
    }, [searchObj]);
    
    useEffect(() => {
        getData()
    }, [location]);
    
    ...

文件夹和文件双击事件

  • 文件夹的双击 文件夹的双击即进入该层文件夹,获取该层文件和文件夹进行渲染
    const handleDbClick = useCallback(dir => {
        // 取得当前点击的文件夹dir路径,然后改变路由
        let url = page + '?prefix=' + dir.dir;
        history.push(url);
    }, [location]);
  • 文件的双击 文件的双击即下载文件
    // 取得配置的文件下载路径
    const downloadPath = settingsStore.get('fileDownloadPath');
    
    const downloadFile = useCallback((file) => {
        // 取得当前文件的id
        const key = file.id;
        // 下载路径的拼接
        let filePath = downloadPath + '/' + +new Date() + '_' + file.filename;
        // 下载文件到指定路径
        manager.downloadFile_v2(key, filePath)
            .then(data => {
                if (data.code === 0) {
                    file.filePath = filePath;
                    file.date = +new Date();
                    // 添加当前用户的下载历史
                    addDownloadHistory(loginInfo.user.id, file);
                    message.success("下载成功")
                } else {
                    message.error("下载失败")
                }
            })
            .catch((err) => {
                message.error("服务器异常")
            })
    }, [searchObj]);