├── server-upload ├── public │ └── upload │ │ └── .gitkeep ├── images │ ├── image-1.jpeg │ ├── image-2.jpeg │ └── image-3.jpeg ├── server-file-upload.js └── file-server.js ├── big-file-upload ├── public │ └── upload │ │ └── .gitkeep ├── big-file-server.js └── big-file-upload.html ├── clipboard-upload ├── public │ └── upload │ │ └── .gitkeep ├── file-server.js └── clipboard-upload.html ├── directory-upload ├── public │ └── upload │ │ └── .gitkeep ├── directory-file-upload.html └── directory-upload-server.js ├── drag-drop-upload ├── public │ └── upload │ │ └── .gitkeep ├── file-server.js └── drag-drop-upload.html ├── single-file-upload ├── public │ └── upload │ │ └── .gitkeep ├── single-file-server.js └── single-file-upload.html ├── multiple-file-upload ├── public │ └── upload │ │ └── .gitkeep ├── multiple-file-server.js └── multiple-file-upload.html ├── directory-compress-upload ├── public │ └── upload │ │ └── .gitkeep ├── file-server.js └── directory-compress-upload.html ├── package.json ├── README.md └── .gitignore /server-upload/public/upload/.gitkeep: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file !.gitkeep -------------------------------------------------------------------------------- /big-file-upload/public/upload/.gitkeep: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file !.gitkeep -------------------------------------------------------------------------------- /clipboard-upload/public/upload/.gitkeep: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file !.gitkeep -------------------------------------------------------------------------------- /directory-upload/public/upload/.gitkeep: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file !.gitkeep -------------------------------------------------------------------------------- /drag-drop-upload/public/upload/.gitkeep: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file !.gitkeep -------------------------------------------------------------------------------- /single-file-upload/public/upload/.gitkeep: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file !.gitkeep -------------------------------------------------------------------------------- /multiple-file-upload/public/upload/.gitkeep: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file !.gitkeep -------------------------------------------------------------------------------- /directory-compress-upload/public/upload/.gitkeep: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file !.gitkeep -------------------------------------------------------------------------------- /server-upload/images/image-1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/semlinker/file-upload-demos/HEAD/server-upload/images/image-1.jpeg -------------------------------------------------------------------------------- /server-upload/images/image-2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/semlinker/file-upload-demos/HEAD/server-upload/images/image-2.jpeg -------------------------------------------------------------------------------- /server-upload/images/image-3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/semlinker/file-upload-demos/HEAD/server-upload/images/image-3.jpeg -------------------------------------------------------------------------------- /server-upload/server-file-upload.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | const FormData = require("form-data"); 4 | 5 | console.log("server-upload"); 6 | // 单文件上传 7 | const form1 = new FormData(); 8 | form1.append( 9 | "file", 10 | fs.createReadStream(path.join(__dirname, "images/image-1.jpeg")) 11 | ); 12 | form1.submit("http://localhost:3000/upload/single", (error, response) => { 13 | if(error) { 14 | console.log("单图上传失败"); 15 | return; 16 | } 17 | console.log("单图上传成功"); 18 | }); 19 | 20 | // 多文件上传 21 | const form2 = new FormData(); 22 | form2.append( 23 | "file", 24 | fs.createReadStream(path.join(__dirname, "images/image-2.jpeg")) 25 | ); 26 | form2.append( 27 | "file", 28 | fs.createReadStream(path.join(__dirname, "images/image-3.jpeg")) 29 | ); 30 | form2.submit("http://localhost:3000/upload/multiple", (error, response) => { 31 | if(error) { 32 | console.log("多图上传失败"); 33 | return; 34 | } 35 | console.log("多图上传成功"); 36 | }); 37 | -------------------------------------------------------------------------------- /directory-compress-upload/file-server.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const Koa = require("koa"); 3 | const cors = require("@koa/cors"); 4 | const serve = require("koa-static"); 5 | const multer = require("@koa/multer"); 6 | const Router = require("@koa/router"); 7 | 8 | const app = new Koa(); 9 | const router = new Router(); 10 | const PORT = 3000; 11 | const RESOURCE_URL = `http://localhost:${PORT}`; 12 | const UPLOAD_DIR = path.join(__dirname, "/public/upload"); 13 | 14 | const storage = multer.diskStorage({ 15 | destination: async function (req, file, cb) { 16 | // 设置文件的存储目录 17 | cb(null, UPLOAD_DIR); 18 | }, 19 | filename: function (req, file, cb) { 20 | // 设置文件名 21 | cb(null, `${file.originalname}`); 22 | }, 23 | }); 24 | 25 | const multerUpload = multer({ storage }); 26 | 27 | router.get("/", async (ctx) => { 28 | ctx.body = "文件上传服务(by 阿宝哥)"; 29 | }); 30 | 31 | router.post( 32 | "/upload/single", 33 | async (ctx, next) => { 34 | try { 35 | await next(); 36 | ctx.body = { 37 | code: 1, 38 | msg: "文件上传成功", 39 | url: `${RESOURCE_URL}/${ctx.file.originalname}`, 40 | }; 41 | } catch (error) { 42 | console.dir(error); 43 | ctx.body = { 44 | code: 0, 45 | msg: "文件上传失败", 46 | }; 47 | } 48 | }, 49 | multerUpload.single("file") 50 | ); 51 | 52 | // 注册中间件 53 | app.use(cors()); 54 | app.use(serve(UPLOAD_DIR)); 55 | app.use(router.routes()).use(router.allowedMethods()); 56 | 57 | app.listen(PORT, () => { 58 | console.log(`应用已经启动:http://localhost:${PORT}/`); 59 | }); 60 | -------------------------------------------------------------------------------- /single-file-upload/single-file-server.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const Koa = require("koa"); 3 | const cors = require("@koa/cors"); 4 | const serve = require("koa-static"); 5 | const multer = require("@koa/multer"); 6 | const Router = require("@koa/router"); 7 | 8 | const app = new Koa(); 9 | const router = new Router(); 10 | const PORT = 3000; 11 | const RESOURCE_URL = `http://localhost:${PORT}`; 12 | const UPLOAD_DIR = path.join(__dirname, "/public/upload"); 13 | 14 | const storage = multer.diskStorage({ 15 | destination: async function (req, file, cb) { 16 | // 设置文件的存储目录 17 | cb(null, UPLOAD_DIR); 18 | }, 19 | filename: function (req, file, cb) { 20 | // 设置文件名 21 | cb(null, `${file.originalname}`); 22 | }, 23 | }); 24 | 25 | const multerUpload = multer({ storage }); 26 | 27 | router.get("/", async (ctx) => { 28 | ctx.body = "文件上传服务(by 阿宝哥)"; 29 | }); 30 | 31 | router.post( 32 | "/upload/single", 33 | async (ctx, next) => { 34 | try { 35 | await next(); 36 | ctx.body = { 37 | code: 1, 38 | msg: "文件上传成功", 39 | url: `${RESOURCE_URL}/${ctx.file.originalname}`, 40 | }; 41 | } catch (error) { 42 | console.dir(error); 43 | ctx.body = { 44 | code: 0, 45 | msg: "文件上传失败", 46 | }; 47 | } 48 | }, 49 | multerUpload.single("file") 50 | ); 51 | 52 | // 注册中间件 53 | app.use(cors()); 54 | app.use(serve(UPLOAD_DIR)); 55 | app.use(router.routes()).use(router.allowedMethods()); 56 | 57 | app.listen(PORT, () => { 58 | console.log(`应用已经启动:http://localhost:${PORT}/`); 59 | }); 60 | -------------------------------------------------------------------------------- /single-file-upload/single-file-upload.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 单文件上传示例 8 | 9 | 10 | 11 |

单文件上传示例

12 | 13 | 14 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /directory-upload/directory-file-upload.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 目录上传示例 8 | 9 | 10 | 11 |

目录上传示例

12 | 13 | 14 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /multiple-file-upload/multiple-file-server.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const Koa = require("koa"); 3 | const cors = require("@koa/cors"); 4 | const serve = require("koa-static"); 5 | const multer = require("@koa/multer"); 6 | const Router = require("@koa/router"); 7 | 8 | const app = new Koa(); 9 | const router = new Router(); 10 | const PORT = 3000; 11 | const RESOURCE_URL = `http://localhost:${PORT}`; 12 | const UPLOAD_DIR = path.join(__dirname, "/public/upload"); 13 | 14 | const storage = multer.diskStorage({ 15 | destination: async function (req, file, cb) { 16 | // 设置文件的存储目录 17 | cb(null, UPLOAD_DIR); 18 | }, 19 | filename: function (req, file, cb) { 20 | // 设置文件名 21 | cb(null, `${file.originalname}`); 22 | }, 23 | }); 24 | 25 | const multerUpload = multer({ storage }); 26 | 27 | router.get("/", async (ctx) => { 28 | ctx.body = "文件上传服务(by 阿宝哥)"; 29 | }); 30 | 31 | router.post( 32 | "/upload/multiple", 33 | async (ctx, next) => { 34 | try { 35 | await next(); 36 | urls = ctx.files.file.map( 37 | (file) => `${RESOURCE_URL}/${file.originalname}` 38 | ); 39 | ctx.body = { 40 | code: 1, 41 | msg: "文件上传成功", 42 | urls, 43 | }; 44 | } catch (error) { 45 | ctx.body = { 46 | code: 0, 47 | msg: "文件上传失败", 48 | }; 49 | } 50 | }, 51 | multerUpload.fields([ 52 | { 53 | name: "file", 54 | }, 55 | ]) 56 | ); 57 | 58 | // 注册中间件 59 | app.use(cors()); 60 | app.use(serve(UPLOAD_DIR)); 61 | app.use(router.routes()).use(router.allowedMethods()); 62 | 63 | app.listen(PORT, () => { 64 | console.log(`应用已经启动:http://localhost:${PORT}/`); 65 | }); 66 | -------------------------------------------------------------------------------- /multiple-file-upload/multiple-file-upload.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 多文件上传示例 8 | 9 | 10 | 11 |

多文件上传示例

12 | 13 | 14 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "file-upload-demos", 3 | "version": "1.0.0", 4 | "description": "Multi-scene File Upload Demo", 5 | "scripts": { 6 | "single": "concurrently \"node ./single-file-upload/single-file-server.js\" \"open ./single-file-upload/single-file-upload.html\"", 7 | "mul": "concurrently \"node ./multiple-file-upload/multiple-file-server.js\" \"open ./multiple-file-upload/multiple-file-upload.html\"", 8 | "dir": "concurrently \"node ./directory-upload/directory-upload-server.js\" \"open ./directory-upload/directory-file-upload.html \"", 9 | "compress": "concurrently \"node ./directory-compress-upload/file-server.js\" \"open ./directory-compress-upload/directory-compress-upload.html\"", 10 | "drag": "concurrently \"node ./drag-drop-upload/file-server.js\" \"open ./drag-drop-upload/drag-drop-upload.html\"", 11 | "clipboard": "concurrently \"node ./clipboard-upload/file-server.js\" \"open ./clipboard-upload/clipboard-upload.html\"", 12 | "big": "concurrently \"node ./big-file-upload/big-file-server.js\" \"open ./big-file-upload/big-file-upload.html\"", 13 | "delay": "node ./node_modules/npm-delay 3000", 14 | "server": "(npm run delay && node ./server-upload/server-file-upload.js) & node ./server-upload/file-server.js" 15 | }, 16 | "keywords": [ 17 | "upload", 18 | "file upload", 19 | "directory upload", 20 | "big file upload" 21 | ], 22 | "author": "semlinker", 23 | "license": "MIT", 24 | "dependencies": { 25 | "@koa/cors": "^3.1.0", 26 | "@koa/multer": "^3.0.0", 27 | "@koa/router": "^10.0.0", 28 | "form-data": "^4.0.0", 29 | "fs-extra": "^10.0.0", 30 | "koa": "^2.13.1", 31 | "koa-static": "^5.0.0", 32 | "multer": "^1.4.2" 33 | }, 34 | "devDependencies": { 35 | "concurrently": "^6.2.0", 36 | "npm-delay": "^1.0.4", 37 | "open": "^8.2.1" 38 | } 39 | } -------------------------------------------------------------------------------- /directory-upload/directory-upload-server.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const Koa = require("koa"); 3 | const cors = require("@koa/cors"); 4 | const serve = require("koa-static"); 5 | const multer = require("@koa/multer"); 6 | const Router = require("@koa/router"); 7 | const fse = require("fs-extra"); 8 | 9 | const app = new Koa(); 10 | const router = new Router(); 11 | const PORT = 3000; 12 | const RESOURCE_URL = `http://localhost:${PORT}`; 13 | const UPLOAD_DIR = path.join(__dirname, "/public/upload"); 14 | 15 | const storage = multer.diskStorage({ 16 | destination: async function (req, file, cb) { 17 | let relativePath = file.originalname.replace(/@/g, path.sep); 18 | let index = relativePath.lastIndexOf(path.sep); 19 | let fileDir = path.join(UPLOAD_DIR, relativePath.substr(0, index)); 20 | await fse.ensureDir(fileDir); 21 | cb(null, fileDir); 22 | }, 23 | filename: function (req, file, cb) { 24 | let parts = file.originalname.split("@"); 25 | cb(null, `${parts[parts.length - 1]}`); 26 | }, 27 | }); 28 | 29 | const multerUpload = multer({ storage }); 30 | 31 | router.get("/", async (ctx) => { 32 | ctx.body = "文件目录上传示例(阿宝哥)"; 33 | }); 34 | 35 | router.post( 36 | "/upload/multiple", 37 | async (ctx, next) => { 38 | try { 39 | await next(); 40 | urls = ctx.files.file.map( 41 | (file) => `${RESOURCE_URL}/${file.originalname.replace(/@/g, path.sep)}` 42 | ); 43 | ctx.body = { 44 | code: 1, 45 | msg: "文件上传成功", 46 | urls, 47 | }; 48 | } catch (error) { 49 | ctx.body = { 50 | code: 0, 51 | msg: "文件上传失败", 52 | }; 53 | } 54 | }, 55 | multerUpload.fields([ 56 | { 57 | name: "file", 58 | }, 59 | ]) 60 | ); 61 | 62 | // 注册中间件 63 | app.use(cors()); 64 | app.use(serve(UPLOAD_DIR)); 65 | app.use(router.routes()).use(router.allowedMethods()); 66 | 67 | app.listen(3000, () => { 68 | console.log("应用已经启动:http://localhost:${PORT}/"); 69 | }); 70 | -------------------------------------------------------------------------------- /server-upload/file-server.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const Koa = require("koa"); 3 | const cors = require("@koa/cors"); 4 | const serve = require("koa-static"); 5 | const multer = require("@koa/multer"); 6 | const Router = require("@koa/router"); 7 | 8 | const app = new Koa(); 9 | const router = new Router(); 10 | const PORT = 3000; 11 | const RESOURCE_URL = `http://localhost:${PORT}`; 12 | const UPLOAD_DIR = path.join(__dirname, "/public/upload"); 13 | 14 | const storage = multer.diskStorage({ 15 | destination: async function (req, file, cb) { 16 | // 设置文件的存储目录 17 | cb(null, UPLOAD_DIR); 18 | }, 19 | filename: function (req, file, cb) { 20 | // 设置文件名 21 | cb(null, `${file.originalname}`); 22 | }, 23 | }); 24 | 25 | const multerUpload = multer({ storage }); 26 | 27 | router.get("/", async (ctx) => { 28 | ctx.body = "文件上传服务(by 阿宝哥)"; 29 | }); 30 | 31 | router.post( 32 | "/upload/multiple", 33 | async (ctx, next) => { 34 | try { 35 | await next(); 36 | urls = ctx.files.file.map(file => `${RESOURCE_URL}/${file.originalname}`); 37 | ctx.body = { 38 | code: 1, 39 | msg: "文件上传成功", 40 | urls 41 | }; 42 | } catch (error) { 43 | ctx.body = { 44 | code: 0, 45 | msg: "文件上传失败", 46 | }; 47 | } 48 | }, 49 | multerUpload.fields([ 50 | { 51 | name: "file", 52 | }, 53 | ]) 54 | ); 55 | 56 | router.post( 57 | "/upload/single", 58 | async (ctx, next) => { 59 | try { 60 | await next(); 61 | ctx.body = { 62 | code: 1, 63 | msg: "文件上传成功", 64 | url: `${RESOURCE_URL}/${ctx.file.originalname}`, 65 | }; 66 | } catch (error) { 67 | console.dir(error); 68 | ctx.body = { 69 | code: 0, 70 | msg: "文件上传失败", 71 | }; 72 | } 73 | }, 74 | multerUpload.single("file") 75 | ); 76 | 77 | // 注册中间件 78 | app.use(cors()); 79 | app.use(serve(UPLOAD_DIR)); 80 | app.use(router.routes()).use(router.allowedMethods()); 81 | 82 | app.listen(PORT, () => { 83 | console.log(`应用已经启动:http://localhost:${PORT}/`); 84 | }); 85 | -------------------------------------------------------------------------------- /drag-drop-upload/file-server.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const Koa = require("koa"); 3 | const cors = require("@koa/cors"); 4 | const serve = require("koa-static"); 5 | const multer = require("@koa/multer"); 6 | const Router = require("@koa/router"); 7 | 8 | const app = new Koa(); 9 | const router = new Router(); 10 | const PORT = 3000; 11 | const RESOURCE_URL = `http://localhost:${PORT}`; 12 | const UPLOAD_DIR = path.join(__dirname, "/public/upload"); 13 | 14 | const storage = multer.diskStorage({ 15 | destination: async function (req, file, cb) { 16 | // 设置文件的存储目录 17 | cb(null, UPLOAD_DIR); 18 | }, 19 | filename: function (req, file, cb) { 20 | // 设置文件名 21 | cb(null, `${file.originalname}`); 22 | }, 23 | }); 24 | 25 | const multerUpload = multer({ storage }); 26 | 27 | router.get("/", async (ctx) => { 28 | ctx.body = "文件上传服务(by 阿宝哥)"; 29 | }); 30 | 31 | router.post( 32 | "/upload/multiple", 33 | async (ctx, next) => { 34 | try { 35 | await next(); 36 | urls = ctx.files.file.map(file => `${RESOURCE_URL}/${file.originalname}`); 37 | ctx.body = { 38 | code: 1, 39 | msg: "文件上传成功", 40 | urls 41 | }; 42 | } catch (error) { 43 | ctx.body = { 44 | code: 0, 45 | msg: "文件上传失败", 46 | }; 47 | } 48 | }, 49 | multerUpload.fields([ 50 | { 51 | name: "file", 52 | }, 53 | ]) 54 | ); 55 | 56 | router.post( 57 | "/upload/single", 58 | async (ctx, next) => { 59 | try { 60 | await next(); 61 | ctx.body = { 62 | code: 1, 63 | msg: "文件上传成功", 64 | url: `${RESOURCE_URL}/${ctx.file.originalname}`, 65 | }; 66 | } catch (error) { 67 | console.dir(error); 68 | ctx.body = { 69 | code: 0, 70 | msg: "文件上传失败", 71 | }; 72 | } 73 | }, 74 | multerUpload.single("file") 75 | ); 76 | 77 | // 注册中间件 78 | app.use(cors()); 79 | app.use(serve(UPLOAD_DIR)); 80 | app.use(router.routes()).use(router.allowedMethods()); 81 | 82 | app.listen(PORT, () => { 83 | console.log(`应用已经启动:http://localhost:${PORT}/`); 84 | }); 85 | -------------------------------------------------------------------------------- /clipboard-upload/file-server.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const Koa = require("koa"); 3 | const cors = require("@koa/cors"); 4 | const serve = require("koa-static"); 5 | const multer = require("@koa/multer"); 6 | const Router = require("@koa/router"); 7 | 8 | const app = new Koa(); 9 | const router = new Router(); 10 | const PORT = 3000; 11 | const RESOURCE_URL = `http://localhost:${PORT}`; 12 | const UPLOAD_DIR = path.join(__dirname, "/public/upload"); 13 | 14 | const storage = multer.diskStorage({ 15 | destination: async function (req, file, cb) { 16 | // 设置文件的存储目录 17 | cb(null, UPLOAD_DIR); 18 | }, 19 | filename: function (req, file, cb) { 20 | // 设置文件名 21 | cb(null, `${file.originalname}`); 22 | }, 23 | }); 24 | 25 | const multerUpload = multer({ storage }); 26 | 27 | router.get("/", async (ctx) => { 28 | ctx.body = "文件上传服务(by 阿宝哥)"; 29 | }); 30 | 31 | router.post( 32 | "/upload/multiple", 33 | async (ctx, next) => { 34 | try { 35 | await next(); 36 | urls = ctx.files.file.map( 37 | (file) => `${RESOURCE_URL}/${file.originalname}` 38 | ); 39 | ctx.body = { 40 | code: 1, 41 | msg: "文件上传成功", 42 | urls, 43 | }; 44 | } catch (error) { 45 | ctx.body = { 46 | code: 0, 47 | msg: "文件上传失败", 48 | }; 49 | } 50 | }, 51 | multerUpload.fields([ 52 | { 53 | name: "file", 54 | }, 55 | ]) 56 | ); 57 | 58 | router.post( 59 | "/upload/single", 60 | async (ctx, next) => { 61 | try { 62 | await next(); 63 | ctx.body = { 64 | code: 1, 65 | msg: "文件上传成功", 66 | url: `${RESOURCE_URL}/${ctx.file.originalname}`, 67 | }; 68 | } catch (error) { 69 | console.dir(error); 70 | ctx.body = { 71 | code: 0, 72 | msg: "文件上传失败", 73 | }; 74 | } 75 | }, 76 | multerUpload.single("file") 77 | ); 78 | 79 | // 注册中间件 80 | app.use(cors()); 81 | app.use(serve(UPLOAD_DIR)); 82 | app.use(router.routes()).use(router.allowedMethods()); 83 | 84 | app.listen(PORT, () => { 85 | console.log(`应用已经启动:http://localhost:${PORT}/`); 86 | }); 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 多场景文件上传示例 2 | 3 | ### 一、项目简介 4 | 5 | 在日常生活中,文件上传是一个很常见的功能,本项目阿宝哥演示了文件上传的八种场景: 6 | 7 | - 单文件上传:利用 `input` 元素的 `accept` 属性限制上传文件的类型、利用 JS 检测文件的类型及使用 [Koa](https://koajs.com/) 实现单文件上传的功能; 8 | 9 | - 多文件上传:利用 `input` 元素的 `multiple` 属性支持选择多文件及使用 [Koa](https://koajs.com/) 实现多文件上传的功能; 10 | 11 | - 目录上传:利用 `input` 元素上的 `webkitdirectory` 属性支持目录上传的功能及使用 [Koa](https://koajs.com/) 实现目录上传并按文件目录结构存放的功能; 12 | - 压缩目录上传:在目录上传的基础上,利用 [JSZip](https://stuk.github.io/jszip/) 实现压缩目录上传的功能; 13 | - 拖拽上传:利用拖拽事件和 [DataTransfer](https://developer.mozilla.org/zh-CN/docs/Web/API/DataTransfer) 对象实现拖拽上传的功能; 14 | - 剪贴板上传:利用剪贴板事件和 [Clipboard](https://developer.mozilla.org/zh-CN/docs/Web/API/Clipboard_API) API 实现剪贴板上传的功能; 15 | - 大文件分块上传:利用 [Blob.slice](https://developer.mozilla.org/zh-CN/docs/Web/API/Blob/slice) 和第三方库 [async-pool](https://github.com/rxaviers/async-pool#readme) 实现大文件并发上传的功能; 16 | - 服务端上传:利用第三方库 [form-data](https://github.com/form-data/form-data) 实现服务端文件流式上传的功能。 17 | 18 | ### 二、项目使用 19 | 20 | #### 2.1 项目初始化 21 | 22 | 1、克隆项目 23 | 24 | ```shell 25 | git clone https://github.com/semlinker/file-upload-demos.git 26 | ``` 27 | 28 | 2、安装依赖 29 | 30 | ```shell 31 | npm install 32 | ``` 33 | 34 | #### 2.2 目录结构说明 35 | 36 | ```shell 37 | ├── README.md #项目说明文档 38 | ├── big-file-upload # 大文件分块上传示例 39 | ├── clipboard-upload # 剪贴板上传示例 40 | ├── directory-compress-upload # 目录压缩上传示例 41 | ├── directory-upload # 目录上传示例 42 | ├── drag-drop-upload # 拖拽上传示例 43 | ├── multiple-file-upload # 多文件上传示例 44 | ├── server-upload # 服务端上传示例 45 | └── single-file-upload # 单文件上传示例 46 | ``` 47 | 48 | #### 2.3 运行单文件上传示例 49 | 50 | ```shell 51 | npm run single 52 | ``` 53 | 54 | #### 2.4 运行多文件上传示例 55 | 56 | ```shell 57 | npm run mul 58 | ``` 59 | 60 | #### 2.5 运行目录上传示例 61 | 62 | ```shell 63 | npm run dir 64 | ``` 65 | 66 | #### 2.6 运行压缩目录上传示例 67 | 68 | ```shell 69 | npm run compress 70 | ``` 71 | 72 | #### 2.7 运行拖拽上传示例 73 | 74 | ```shell 75 | npm run drag 76 | ``` 77 | 78 | #### 2.8 运行剪贴板上传示例 79 | 80 | ```shell 81 | npm run clipboard 82 | ``` 83 | 84 | > 提示:测试剪贴板上传示例的时候,图片来源可以从网页上复制图片或从微信聊天框中复制图片。 85 | 86 | #### 2.9 运行大文件分块上传示例 87 | 88 | ```shell 89 | npm run big 90 | ``` 91 | 92 | #### 2.10 运行服务端上传示例 93 | 94 | ```shell 95 | npm run server 96 | ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | .DS_Store 19 | 20 | # Directory for instrumented libs generated by jscoverage/JSCover 21 | lib-cov 22 | 23 | # Coverage directory used by tools like istanbul 24 | coverage 25 | *.lcov 26 | 27 | # nyc test coverage 28 | .nyc_output 29 | 30 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 31 | .grunt 32 | 33 | # Bower dependency directory (https://bower.io/) 34 | bower_components 35 | 36 | # node-waf configuration 37 | .lock-wscript 38 | 39 | # Compiled binary addons (https://nodejs.org/api/addons.html) 40 | build/Release 41 | 42 | # Dependency directories 43 | node_modules/ 44 | jspm_packages/ 45 | 46 | # Snowpack dependency directory (https://snowpack.dev/) 47 | web_modules/ 48 | 49 | # TypeScript cache 50 | *.tsbuildinfo 51 | 52 | # Optional npm cache directory 53 | .npm 54 | 55 | # Optional eslint cache 56 | .eslintcache 57 | 58 | # Microbundle cache 59 | .rpt2_cache/ 60 | .rts2_cache_cjs/ 61 | .rts2_cache_es/ 62 | .rts2_cache_umd/ 63 | 64 | # Optional REPL history 65 | .node_repl_history 66 | 67 | # Output of 'npm pack' 68 | *.tgz 69 | 70 | # Yarn Integrity file 71 | .yarn-integrity 72 | 73 | # dotenv environment variables file 74 | .env 75 | .env.test 76 | .env.production 77 | 78 | # parcel-bundler cache (https://parceljs.org/) 79 | .cache 80 | .parcel-cache 81 | 82 | # Next.js build output 83 | .next 84 | out 85 | 86 | # Nuxt.js build / generate output 87 | .nuxt 88 | dist 89 | 90 | # Gatsby files 91 | .cache/ 92 | # Comment in the public line in if your project uses Gatsby and not Next.js 93 | # https://nextjs.org/blog/next-9-1#public-directory-support 94 | # public 95 | 96 | # vuepress build output 97 | .vuepress/dist 98 | 99 | # Serverless directories 100 | .serverless/ 101 | 102 | # FuseBox cache 103 | .fusebox/ 104 | 105 | # DynamoDB Local files 106 | .dynamodb/ 107 | 108 | # TernJS port file 109 | .tern-port 110 | 111 | # Stores VSCode versions used for testing VSCode extensions 112 | .vscode-test 113 | 114 | # yarn v2 115 | .yarn/cache 116 | .yarn/unplugged 117 | .yarn/build-state.yml 118 | .yarn/install-state.gz 119 | .pnp.* 120 | -------------------------------------------------------------------------------- /directory-compress-upload/directory-compress-upload.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 目录压缩上传示例 8 | 9 | 10 | 11 | 12 |

目录压缩上传示例

13 | 14 | 15 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /clipboard-upload/clipboard-upload.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 剪贴板上传示例 8 | 9 | 28 | 29 | 30 |

剪贴板上传示例

31 |
32 |

请先复制图片后再执行粘贴操作

33 |
34 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /big-file-upload/big-file-server.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | const util = require("util"); 4 | const Koa = require("koa"); 5 | const cors = require("@koa/cors"); 6 | const multer = require("@koa/multer"); 7 | const Router = require("@koa/router"); 8 | const serve = require("koa-static"); 9 | const fse = require("fs-extra"); 10 | const readdir = util.promisify(fs.readdir); 11 | const unlink = util.promisify(fs.unlink); 12 | 13 | const app = new Koa(); 14 | const router = new Router(); 15 | const TMP_DIR = path.join(__dirname, "tmp"); // 临时目录 16 | const UPLOAD_DIR = path.join(__dirname, "/public/upload"); 17 | const IGNORES = [".DS_Store"]; // 忽略的文件列表 18 | 19 | const storage = multer.diskStorage({ 20 | destination: async function (req, file, cb) { 21 | let fileMd5 = file.originalname.split("-")[0]; 22 | const fileDir = path.join(TMP_DIR, fileMd5); 23 | await fse.ensureDir(fileDir); 24 | cb(null, fileDir); 25 | }, 26 | filename: function (req, file, cb) { 27 | let chunkIndex = file.originalname.split("-")[1]; 28 | cb(null, `${chunkIndex}`); 29 | }, 30 | }); 31 | 32 | const multerUpload = multer({ storage }); 33 | 34 | router.get("/", async (ctx) => { 35 | ctx.body = "大文件并发上传示例(阿宝哥)"; 36 | }); 37 | 38 | router.get("/upload/exists", async (ctx) => { 39 | const { name: fileName, md5: fileMd5 } = ctx.query; 40 | const filePath = path.join(UPLOAD_DIR, fileName); 41 | const isExists = await fse.pathExists(filePath); 42 | if (isExists) { 43 | ctx.body = { 44 | status: "success", 45 | data: { 46 | isExists: true, 47 | url: `http://localhost:3000/${fileName}`, 48 | }, 49 | }; 50 | } else { 51 | let chunkIds = []; 52 | const chunksPath = path.join(TMP_DIR, fileMd5); 53 | const hasChunksPath = await fse.pathExists(chunksPath); 54 | if (hasChunksPath) { 55 | let files = await readdir(chunksPath); 56 | chunkIds = files.filter((file) => { 57 | return IGNORES.indexOf(file) === -1; 58 | }); 59 | } 60 | ctx.body = { 61 | status: "success", 62 | data: { 63 | isExists: false, 64 | chunkIds, 65 | }, 66 | }; 67 | } 68 | }); 69 | 70 | router.post( 71 | "/upload/single", 72 | multerUpload.single("file"), 73 | async (ctx, next) => { 74 | ctx.body = { 75 | code: 1, 76 | data: ctx.file, 77 | }; 78 | } 79 | ); 80 | 81 | router.get("/upload/concatFiles", async (ctx) => { 82 | const { name: fileName, md5: fileMd5 } = ctx.query; 83 | await concatFiles( 84 | path.join(TMP_DIR, fileMd5), 85 | path.join(UPLOAD_DIR, fileName) 86 | ); 87 | ctx.body = { 88 | status: "success", 89 | data: { 90 | url: `http://localhost:3000/${fileName}`, 91 | }, 92 | }; 93 | }); 94 | 95 | async function concatFiles(sourceDir, targetPath) { 96 | const readFile = (file, ws) => 97 | new Promise((resolve, reject) => { 98 | fs.createReadStream(file) 99 | .on("data", (data) => ws.write(data)) 100 | .on("end", resolve) 101 | .on("error", reject); 102 | }); 103 | const files = await readdir(sourceDir); 104 | const sortedFiles = files 105 | .filter((file) => { 106 | return IGNORES.indexOf(file) === -1; 107 | }) 108 | .sort((a, b) => a - b); 109 | const writeStream = fs.createWriteStream(targetPath); 110 | for (const file of sortedFiles) { 111 | let filePath = path.join(sourceDir, file); 112 | await readFile(filePath, writeStream); 113 | await unlink(filePath); // 删除已合并的分块 114 | } 115 | writeStream.end(); 116 | } 117 | 118 | // 注册中间件 119 | app.use(cors()); 120 | app.use(serve(UPLOAD_DIR)); 121 | app.use(router.routes()).use(router.allowedMethods()); 122 | 123 | app.listen(3000, () => { 124 | console.log("app starting at port 3000"); 125 | }); -------------------------------------------------------------------------------- /drag-drop-upload/drag-drop-upload.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 拖拽上传示例 8 | 9 | 33 | 34 | 35 |

拖拽上传示例

36 |
37 |

拖拽上传文件

38 |
39 |
40 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /big-file-upload/big-file-upload.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 大文件并发上传示例 8 | 9 | 10 | 11 | 12 |

大文件并发上传示例

13 | 14 | 15 | 158 | 159 | 160 | --------------------------------------------------------------------------------