Skip to content
关注公众号,获取新课通知

上传文件


插件 egg-oss

安装

shell
npm i egg-oss --save

文档

https://www.npmjs.com/package/egg-oss

配置

config/plugin.js

js
oss: {
    enable: true,
    package: 'egg-oss',
  }

配置:config/config.default.js

js
config.oss = {
  client: {
    accessKeyId: "LTAI4DS1yG3E9z3dGijcqEvv",
    accessKeySecret: "cwFVsXF5S5n75a8NZIz0IS2EO5pqvl",
    bucket: "demo-mp3",
    endpoint: "oss-cn-shenzhen.aliyuncs.com",
    timeout: "60s",
  },
};

// 上传格式和大小限制
config.multipart = {
  // fileSize: '50mb',
  fileSize: 1048576000,
  // mode: 'stream',
  mode: "file",
  fileExtensions: [
    // images
    ".jpg",
    ".jpeg", // image/jpeg
    ".png", // image/png, image/x-png
    ".gif", // image/gif
    ".bmp", // image/bmp
    ".wbmp", // image/vnd.wap.wbmp
    ".webp",
    ".tif",
    ".psd",
    // text
    ".svg",
    ".js",
    ".jsx",
    ".json",
    ".css",
    ".less",
    ".html",
    ".htm",
    ".xml",
    // tar
    ".zip",
    ".gz",
    ".tgz",
    ".gzip",
    // video
    ".mp3",
    ".mp4",
    ".avi",
  ],
};

创建数据迁移表

shell
npx sequelize migration:generate --name=file

1.执行完命令后,会在database / migrations / 目录下生成数据表迁移文件,然后定义

js
"use strict";

module.exports = {
  up: (queryInterface, Sequelize) => {
    const { INTEGER, STRING, DATE, ENUM, TEXT } = Sequelize;
    return queryInterface.createTable("file", {
      id: {
        type: INTEGER(20),
        primaryKey: true,
        autoIncrement: true,
      },
      name: {
        type: STRING(100),
        allowNull: false,
        defaultValue: "",
        comment: "文件名",
      },
      ext: {
        type: STRING(50),
        allowNull: true,
        defaultValue: "",
        comment: "文件扩展名",
      },
      md: {
        type: STRING,
        allowNull: true,
        defaultValue: "",
        comment: "文件MD5",
      },
      file_id: {
        type: INTEGER,
        allowNull: false,
        defaultValue: 0,
        comment: "父级id",
      },
      user_id: {
        type: INTEGER,
        allowNull: false,
        defaultValue: 0,
        comment: "用户id",
        references: {
          model: "user",
          key: "id",
        },
        onDelete: "cascade",
        onUpdate: "restrict", // 更新时操作
      },
      size: {
        type: INTEGER,
        allowNull: false,
        defaultValue: 0,
        comment: "文件大小",
      },
      url: {
        type: STRING,
        allowNull: true,
        defaultValue: "",
        comment: "图片真实url",
      },
      isdir: {
        type: INTEGER,
        allowNull: false,
        defaultValue: 0,
        comment: "是否为文件夹",
      },
      created_time: DATE,
      updated_time: DATE,
    });
  },

  down: (queryInterface, Sequelize) => {
    return queryInterface.dropTable("file");
  },
};
  • 执行 migrate 进行数据库变更
shell
npx sequelize db:migrate

模型创建

js
// app/model/file.js
module.exports = (app) => {
  const { STRING, INTEGER, DATE, ENUM, TEXT } = app.Sequelize;

  const File = app.model.define("file", {
    id: {
      type: INTEGER(20),
      primaryKey: true,
      autoIncrement: true,
    },
    name: {
      type: STRING(100),
      allowNull: false,
      defaultValue: "",
      comment: "文件名",
    },
    ext: {
      type: STRING(50),
      allowNull: true,
      defaultValue: "",
      comment: "文件扩展名",
    },
    md: {
      type: STRING,
      allowNull: true,
      defaultValue: "",
      comment: "文件MD5",
    },
    file_id: {
      type: INTEGER,
      allowNull: false,
      defaultValue: 0,
      comment: "父级id",
    },
    user_id: {
      type: INTEGER,
      allowNull: false,
      defaultValue: 0,
      comment: "用户id",
      references: {
        model: "user",
        key: "id",
      },
      onDelete: "cascade",
      onUpdate: "restrict", // 更新时操作
    },
    size: {
      type: INTEGER,
      allowNull: false,
      defaultValue: 0,
      comment: "文件大小",
    },
    url: {
      type: STRING,
      allowNull: true,
      defaultValue: "",
      comment: "图片真实url",
    },
    isdir: {
      type: INTEGER,
      allowNull: false,
      defaultValue: 0,
      comment: "是否为文件夹",
    },
    created_time: DATE,
    updated_time: DATE,
  });

  // 删除后
  File.afterBulkDestroy(async (data, option) => {
    console.log("删除后", data.where);

    let files = await app.model.File.findAll({
      where: {
        file_id: data.where.id,
        user_id: data.where.user_id,
        isdir: 1,
      },
    });

    let ids = files.map((item) => item.id);

    if (ids.length > 0) {
      app.model.File.destroy({
        where: {
          id: ids,
          user_id: data.where.user_id,
        },
      });
    }
  });

  return File;
};

控制器:app/controller/file.js

js
	// 引入
	const fs = require('fs');
	const path = require('path');
	
	// 上传
	async upload() {
        const { ctx, app } = this;
        const currentUser = ctx.authUser;

        if (!ctx.request.files) {
            return ctx.apiFail('请先选择上传文件');
        }

        ctx.validate({
            file_id: {
                type: "int",
                required: true,
                defValue: 0,
                desc: 'file_id'
            },
        });

        const file_id = ctx.query.file_id;

        // 文件id是否存在
        if (file_id > 0) {
			// 目录是否存在
            await this.service.file.isDirExist(file_id);
        }

        const file = ctx.request.files[0];
        const name = 'egg-oss-demo/' + ctx.genID(10) + path.extname(file.filename);

        // 验证用户剩余内存是否满足要求 
        let s = await (new Promise((resolve, reject) => {
            fs.stat(file.filepath, (err, stats) => {
                resolve((stats.size / 1024).toFixed(1));
            });
        }));

        if ((currentUser.total_size - currentUser.used_size) < s) {
            return ctx.apiFail('你的可用内存不足');
        }

        let result;
        try {
            result = await ctx.oss.put(name, file.filepath);
        } catch (err) {
            console.log(err);
        }

        if (result) {
            // 写入数据表
            let addData = {
                name: file.filename,
                ext: file.mimeType,
                md: result.name,
                file_id,
                user_id: currentUser.id,
                size: parseInt(s),
                isdir: 0,
                url: result.url
            };

            if (file_id > 0) {
                addData.file_id = file_id;
            }

            let res = await app.model.File.create(addData);

            // 更新user表的使用内存
            currentUser.used_size = currentUser.used_size + parseInt(s);
            currentUser.save();

            return ctx.apiSuccess(res);
        }

        ctx.apiFail('上传失败');
    }

服务:app/service/file.js

js
	// 目录是否存在
	async isDirExist(id) {
        let f = await this.app.model.File.findOne({
            where: {
                id,
				// 当前用户id
                user_id: this.ctx.authUser.id,
                isdir: 1
            }
        });

        if (!f) {
            return this.ctx.throw(404, '目录不存在');
        }

        return f
    }

扩展:app/extend/context.js

js
// 生成唯一id
   genID(length) {
       return Number(Math.random().toString().substr(3, length) + Date.now()).toString(36);
   }

路由:app/router.js

js
router.post("/upload", controller.file.upload);