├── 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 |
22 | 23 | 24 |
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 | } --------------------------------------------------------------------------------