├── 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 |
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 |
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 |
--------------------------------------------------------------------------------