├── receive
└── .gitkeep
├── index.js
├── .babelrc
├── Makefile
├── README.md
├── test
└── app.spec.js
├── .gitignore
├── app.js
├── md5.js
├── package.json
└── upload.js
/receive/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | require("babel-register");
2 | require("./app.js");
3 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015-node5"],
3 | "plugins": [
4 | "transform-async-to-generator",
5 | "syntax-async-functions"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: test
2 | test:
3 | @NODE_ENV=test ./node_modules/.bin/mocha \
4 | --compilers js:babel-register \
5 | --harmony \
6 | --reporter spec \
7 | --require should \
8 | test/*.js
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # file upload example server
2 | 使用Node.js实现文件流转存服务 测试的后端server
3 |
4 | ## Installation
5 |
6 | ```
7 | npm install
8 | npm start
9 | ```
10 |
11 | ## How to Use
12 |
13 | npm start 启动之后, 默认监听本地3000端口。
14 | 接收到的文件会保存在receive文件夹下面。
15 |
16 | ## 代码解释
17 |
18 | 发送端会在filename内部使用特殊的字符串:`_IDSPLIT_` 来分割分片的index和文件名。
19 | 来保证服务端在连续收到来自多个文件的不同分片的时候,也能够进行区分。
--------------------------------------------------------------------------------
/test/app.spec.js:
--------------------------------------------------------------------------------
1 | import app from '../app'
2 | import supertest from 'supertest'
3 |
4 | const request = supertest.agent(app.listen())
5 |
6 | describe('Hello World', function () {
7 | it('should say "Hello World"', function (done) {
8 | request
9 | .get('/')
10 | .expect(200)
11 | .expect('Hello World', done)
12 | })
13 | })
14 |
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Coverage directory used by tools like istanbul
12 | coverage
13 |
14 | # node-waf configuration
15 | .lock-wscript
16 |
17 | # Dependency directory
18 | node_modules
19 |
20 | # Optional npm cache directory
21 | .npm
22 |
23 | # Optional REPL history
24 | .node_repl_history
25 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | import Koa from 'koa'
2 | import logger from 'koa-logger'
3 | import path from 'path'
4 | import fs from 'fs'
5 | import router from 'koa-route'
6 |
7 | import upload from './upload'
8 | import md5 from './md5'
9 |
10 | const app = new Koa()
11 |
12 | app.use(logger());
13 |
14 | app.use(router.get('/', function* () {
15 | this.body = `
16 |
17 |
18 |
19 |
20 |
21 |
25 |
26 | `
27 | }));
28 | app.use(router.post('/upload', upload));
29 | app.use(router.post('/md5', md5));
30 |
31 | app.listen(3000, () => console.log('server started 3000'))
32 |
33 | export default app
34 |
35 |
--------------------------------------------------------------------------------
/md5.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import parse from 'co-body'
3 | import path from 'path'
4 | import crypto from 'crypto'
5 |
6 | const receiveRootDir = path.join(__dirname, 'receive');
7 |
8 | function toMd5(buffer) {
9 | let md5 = crypto.createHash('md5');
10 | md5.update(buffer);
11 | return md5.digest('hex');
12 | }
13 |
14 | module.exports = function* () {
15 | var body = yield parse(this, {
16 | limit: '1kb'
17 | });
18 |
19 | let filename = body.filename;
20 | let fileDir = path.join(receiveRootDir, filename);
21 |
22 | let totalBuffer = Buffer.alloc(0);
23 | let dirInfo = fs.readdirSync(fileDir);
24 |
25 | dirInfo = dirInfo.sort(function(pre, next) {
26 | return pre - next;
27 | });
28 |
29 | dirInfo.forEach(name => {
30 | let chunkPath = path.join(fileDir, name);
31 | let buffer = fs.readFileSync(chunkPath);
32 | totalBuffer = Buffer.concat([totalBuffer, buffer], totalBuffer.length + buffer.length);
33 | })
34 |
35 | this.body = {
36 | error: 0,
37 | data: toMd5(totalBuffer)
38 | }
39 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "koa2-boilerplate",
3 | "version": "1.0.0",
4 | "description": "Minimal koa v2 boilerplate.",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "make test",
8 | "start": "./node_modules/.bin/nodemon"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/geekplux/koa2-boilerplate.git"
13 | },
14 | "keywords": [
15 | "koa",
16 | "koa2",
17 | "babel",
18 | "es2015"
19 | ],
20 | "author": "GeekPlux",
21 | "license": "MIT",
22 | "bugs": {
23 | "url": "https://github.com/geekplux/koa2-boilerplate/issues"
24 | },
25 | "homepage": "https://github.com/geekplux/koa2-boilerplate#readme",
26 | "dependencies": {
27 | "co-body": "^4.2.0",
28 | "co-busboy": "^1.3.1",
29 | "koa": "^2.0.0-alpha.3",
30 | "koa-logger": "^1.3.1",
31 | "koa-route": "^2.4.2"
32 | },
33 | "devDependencies": {
34 | "babel-plugin-syntax-async-functions": "^6.8.0",
35 | "babel-plugin-transform-async-to-generator": "^6.8.0",
36 | "babel-preset-es2015-node5": "^1.1.2",
37 | "babel-preset-stage-0": "^6.3.13",
38 | "babel-register": "^6.4.3",
39 | "mocha": "^2.3.4",
40 | "nodemon": "^1.8.1",
41 | "should": "^8.2.0",
42 | "supertest": "^1.1.0"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/upload.js:
--------------------------------------------------------------------------------
1 | import parse from 'co-busboy'
2 | import path from 'path'
3 | import fs from 'fs'
4 |
5 | const outputDir = path.join(__dirname, 'receive');
6 |
7 | module.exports = function* () {
8 | // ignore non-POSTs
9 | if ('POST' != this.method) return yield next;
10 |
11 | // multipart upload
12 | var parts = parse(this);
13 | var part;
14 |
15 | while ((part = yield parts)) {
16 | if (part.constructor.name === 'FileStream') {
17 | let filename = part.filename;
18 | let chunkIndex, prefix;
19 |
20 | if (filename.indexOf('_IDSPLIT_') > 0) {
21 | prefix = filename.split('_IDSPLIT_')[0];
22 | chunkIndex = filename.split('_IDSPLIT_')[1];
23 |
24 | // // // 错误处理测试使用
25 | if (chunkIndex == 1) {
26 | // console.log(`set failed of ${chunkIndex}`);
27 | // this.status = 500;
28 | }
29 | }
30 | else {
31 | prefix = filename;
32 | chunkIndex = '0';
33 | }
34 |
35 | let fileDir = path.join(outputDir, prefix)
36 |
37 | if (!fs.existsSync(fileDir)) {
38 | fs.mkdirSync(fileDir);
39 | }
40 |
41 | var stream = fs.createWriteStream(path.join(fileDir, chunkIndex));
42 | part.pipe(stream);
43 | console.log('uploading %s -> %s', part.filename, stream.path);
44 | }
45 | else {
46 | let param = part[0];
47 | let value = part[1];
48 | console.log(`${param} : ${value}`);
49 | }
50 | }
51 |
52 | this.body = {
53 | error: 0,
54 | errmsg: 'ok'
55 | }
56 | }
--------------------------------------------------------------------------------