dygapp 6 年 前
コミット
db4bdae68b

+ 30 - 0
.autod.conf.js

@@ -0,0 +1,30 @@
+'use strict';
+
+module.exports = {
+  write: true,
+  prefix: '^',
+  plugin: 'autod-egg',
+  test: [
+    'test',
+    'benchmark',
+  ],
+  dep: [
+    'egg',
+    'egg-scripts',
+  ],
+  devdep: [
+    'egg-ci',
+    'egg-bin',
+    'egg-mock',
+    'autod',
+    'autod-egg',
+    'eslint',
+    'eslint-config-egg',
+    'webstorm-disable-index',
+  ],
+  exclude: [
+    './test/fixtures',
+    './dist',
+  ],
+};
+

+ 1 - 0
.eslintignore

@@ -0,0 +1 @@
+coverage

+ 3 - 0
.eslintrc

@@ -0,0 +1,3 @@
+{
+  "extends": "eslint-config-egg"
+}

+ 15 - 0
.gitignore

@@ -0,0 +1,15 @@
+logs/
+npm-debug.log
+yarn-error.log
+node_modules/
+package-lock.json
+yarn.lock
+coverage/
+.idea/
+run/
+.DS_Store
+*.sw*
+*.un~
+.travis.yml
+appveyor.yml
+upload/

+ 33 - 0
README.md

@@ -0,0 +1,33 @@
+# filestore
+
+
+
+## QuickStart
+
+<!-- add docs here for user -->
+
+see [egg docs][egg] for more detail.
+
+### Development
+
+```bash
+$ npm i
+$ npm run dev
+$ open http://localhost:7001/
+```
+
+### Deploy
+
+```bash
+$ npm start
+$ npm stop
+```
+
+### npm scripts
+
+- Use `npm run lint` to check code style.
+- Use `npm test` to run unit test.
+- Use `npm run autod` to auto detect dependencies upgrade, see [autod](https://www.npmjs.com/package/autod) for more detail.
+
+
+[egg]: https://eggjs.org

+ 39 - 0
README.zh-CN.md

@@ -0,0 +1,39 @@
+# filestore
+
+
+
+## 快速入门
+
+<!-- 在此次添加使用文档 -->
+
+如需进一步了解,参见 [egg 文档][egg]。
+
+### 本地开发
+
+```bash
+$ npm i
+$ npm run dev
+$ open http://localhost:7001/
+```
+
+### 部署
+
+```bash
+$ npm start
+$ npm stop
+```
+
+### 单元测试
+
+- [egg-bin] 内置了 [mocha], [thunk-mocha], [power-assert], [istanbul] 等框架,让你可以专注于写单元测试,无需理会配套工具。
+- 断言库非常推荐使用 [power-assert]。
+- 具体参见 [egg 文档 - 单元测试](https://eggjs.org/zh-cn/core/unittest)。
+
+### 内置指令
+
+- 使用 `npm run lint` 来做代码风格检查。
+- 使用 `npm test` 来执行单元测试。
+- 使用 `npm run autod` 来自动检测依赖更新,详细参见 [autod](https://www.npmjs.com/package/autod) 。
+
+
+[egg]: https://eggjs.org

+ 64 - 0
app/controller/files.js

@@ -0,0 +1,64 @@
+'use strict';
+
+const Controller = require('egg').Controller;
+const moment = require('moment');
+const { sep, extname } = require('path');
+const fs = require('fs');
+const awaitWriteStream = require('await-stream-ready').write;
+const sendToWormhole = require('stream-wormhole');
+const assert = require('assert');
+
+class FilesController extends Controller {
+
+  async upload() {
+    const { ctx, app } = this;
+    const { appid, catalog } = ctx.params;
+    assert(appid);
+    const stream = await ctx.getFileStream();
+    ctx.logger.debug(stream);
+
+    const rootPath = `${app.config.cdn.repos_root_path}`;
+    const rootUrl = `${app.config.cdn.repos_root_url}`;
+    const dirs = [ appid ];
+    if (catalog) {
+      dirs.push(catalog);
+    }
+    const saved = await this.saveFile(rootPath, dirs, stream);
+    const uri = `${rootUrl}/${dirs.join('/')}/${saved.fileName}`;
+    ctx.body = { errcode: 0, errmsg: 'ok', id: saved.id, name: saved.fileName, uri };
+  }
+
+  async saveFile(rootPath, dirs, stream) {
+    const ctx = this.ctx;
+    const ext = extname(stream.filename).toLowerCase();
+    const name = moment().format('YYYYMMDDHHmmss');
+    // TODO: 检查根路径是否存在,不存在则创建
+    ctx.logger.debug('rootPath: ', rootPath);
+    if (!fs.existsSync(rootPath)) {
+      ctx.logger.debug('create dir: ', rootPath);
+      fs.mkdirSync(rootPath);
+    }
+    // TODO: 检查分级目录是否存在,不存在则创建
+    for (let i = 0; i < dirs.length; i++) {
+      const p = `${rootPath}${sep}${dirs.slice(0, i + 1).join(sep)}`;
+      if (!fs.existsSync(p)) {
+        fs.mkdirSync(p);
+      }
+    }
+
+    const filePath = `${rootPath}${sep}${dirs.join(sep)}${sep}`;
+    const fileName = `${name}${ext}`;
+    const writeStream = fs.createWriteStream(filePath + fileName);
+    try {
+      await awaitWriteStream(stream.pipe(writeStream));
+    } catch (err) {
+      await sendToWormhole(stream);
+      throw err;
+    }
+
+    return { filePath, fileName, id: name };
+  }
+
+}
+
+module.exports = FilesController;

+ 11 - 0
app/controller/home.js

@@ -0,0 +1,11 @@
+'use strict';
+
+const Controller = require('egg').Controller;
+
+class HomeController extends Controller {
+  async index() {
+    this.ctx.body = 'hi, egg';
+  }
+}
+
+module.exports = HomeController;

+ 62 - 0
app/public/demo.html

@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+  <meta charset="UTF-8" />
+  <meta name="viewport" content="width=device-width,height=device-height,initial-scale=1.0,maximum-scale=1.0,user-scalable=yes"
+  />
+  <title>文件上传</title>
+  <script src="//unpkg.com/vue/dist/vue.js"></script>
+  <script src="//unpkg.com/element-ui@2.0.4/lib/index.js"></script>
+  <!-- 引入样式 -->
+  <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"></link>
+
+</head>
+
+<body>
+  <div id="app">
+  <el-upload action="/files/demo/upload" list-type="picture-card" :on-preview="handlePictureCardPreview" :on-remove="handleRemove"
+    :on-success="handleUploadSuccess">
+    <i class="el-icon-plus"></i>
+  </el-upload>
+  <el-dialog :visible.sync="dialogVisible" size="tiny">
+    <img width="100%" :src="dialogImageUrl" alt="">
+  </el-dialog>
+  </div>
+  <script>
+    var Main = {
+      data() {
+        return {
+          dialogImageUrl: '',
+          dialogVisible: false
+        };
+      },
+      methods: {
+        handleRemove(file, fileList) {
+          console.log(file, fileList);
+        },
+        handlePictureCardPreview(file) {
+          this.dialogImageUrl = file.url;
+          this.dialogVisible = true;
+        },
+        handleUploadSuccess(res, file) {
+          console.log({ res, file });
+          if (res.errcode !== 0) {
+            this.$notify.error({
+              title: '错误',
+              message: '文件上传失败',
+            });
+            return;
+          }
+          // this.imageUrl = URL.createObjectURL(file.raw);
+          // this.partyForm.image = res.id;
+          this.$message({ type: 'success', message: '文件上传成功' });
+        },
+      }
+    };
+    var Ctor = Vue.extend(Main)
+    var app = new Ctor().$mount('#app')
+
+  </script>
+
+</body>

+ 11 - 0
app/router.js

@@ -0,0 +1,11 @@
+'use strict';
+
+/**
+ * @param {Egg.Application} app - egg application
+ */
+module.exports = app => {
+  const { router, controller } = app;
+  router.get('/', controller.home.index);
+  router.post('/:appid/upload', controller.files.upload);
+  router.post('/:appid/:catalog/upload', controller.files.upload);
+};

+ 36 - 0
config/config.default.js

@@ -0,0 +1,36 @@
+'use strict';
+
+const { sep } = require('path');
+const ErrorConfig = require('./config.error.js');
+
+module.exports = appInfo => {
+  const config = exports = {};
+
+  // use for cookie sign key, should change to your own and keep security
+  config.keys = appInfo.name + '_1515578157616_3792';
+
+  // add your config here
+  config.middleware = [];
+  config.cluster = {
+    listen: {
+      port: 8009,
+    },
+  };
+
+  // 安全配置
+  config.security = {
+    csrf: {
+      // ignoreJSON: true, // 默认为 false,当设置为 true 时,将会放过所有 content-type 为 `application/json` 的请求
+      enable: false,
+    },
+  };
+
+  config.cdn = {
+    repos_root_path: `${appInfo.baseDir}${sep}upload`,
+    repos_root_url: '',
+  };
+
+  config.onerror = ErrorConfig;
+
+  return config;
+};

+ 26 - 0
config/config.error.js

@@ -0,0 +1,26 @@
+'use strict';
+
+const { NafError, BusinessError } = require('naf-core').Error;
+
+module.exports = {
+  json(err, ctx) {
+    // json hander
+    if (err instanceof BusinessError) {
+      // 业务错误
+      ctx.body = { errcode: err.errcode, errmsg: err.errmsg };
+      ctx.status = 200;
+    } else if (err instanceof NafError) {
+      // 框架错误
+      ctx.body = { errcode: err.errcode, errmsg: err.errmsg };
+      ctx.status = 500;
+    } else if (err instanceof Error) {
+      // 其他错误
+      ctx.body = { errcode: 500, errmsg: '系统错误', details: err.message };
+      ctx.status = 500;
+    } else {
+      // 未知错误
+      ctx.body = { errcode: 500, errmsg: '未知错误', details: err };
+      ctx.status = 500;
+    }
+  },
+};

+ 14 - 0
config/config.local.js

@@ -0,0 +1,14 @@
+'use strict';
+
+const { sep } = require('path');
+
+module.exports = appInfo => {
+  const config = exports = {};
+
+  config.cdn = {
+    repos_root_path: `${appInfo.baseDir}${sep}upload`,
+    repos_root_url: '/files',
+  };
+
+  return config;
+};

+ 14 - 0
config/config.prod.js

@@ -0,0 +1,14 @@
+'use strict';
+
+const { sep } = require('path');
+
+module.exports = appInfo => {
+  const config = exports = {};
+
+  config.cdn = {
+    repos_root_path: `${appInfo.baseDir}${sep}upload`,
+    repos_root_url: '/files',
+  };
+
+  return config;
+};

+ 4 - 0
config/plugin.js

@@ -0,0 +1,4 @@
+'use strict';
+
+// had enabled by egg
+// exports.static = true;

+ 17 - 0
ecosystem.config.js

@@ -0,0 +1,17 @@
+'use strict';
+
+const app = 'filestore';
+module.exports = {
+  apps: [{
+    name: app, // 应用名称
+    script: './server.js', // 实际启动脚本
+    out: `./logs/${app}.log`,
+    error: `./logs/${app}.err`,
+    watch: [ // 监控变化的目录,一旦变化,自动重启
+      'app', 'config',
+    ],
+    env: {
+      NODE_ENV: 'production', // 环境参数,当前指定为生产环境
+    },
+  }],
+};

+ 50 - 0
package.json

@@ -0,0 +1,50 @@
+{
+  "name": "filestore",
+  "version": "1.0.0",
+  "description": "",
+  "private": true,
+  "dependencies": {
+    "await-stream-ready": "^1.0.1",
+    "egg": "^2.14.1",
+    "egg-scripts": "^2.10.0",
+    "moment": "^2.22.2",
+    "mongodb": "^3.1.10",
+    "naf-core": "0.0.27",
+    "stream-wormhole": "^1.1.0"
+  },
+  "devDependencies": {
+    "autod": "^3.0.1",
+    "autod-egg": "^1.1.0",
+    "egg-bin": "^4.9.0",
+    "egg-ci": "^1.10.0",
+    "egg-mock": "^3.20.1",
+    "eslint": "^5.10.0",
+    "webstorm-disable-index": "^1.2.0"
+  },
+  "engines": {
+    "node": ">=8.9.0"
+  },
+  "scripts": {
+    "start": "egg-scripts start --daemon --title=filestore",
+    "stop": "egg-scripts stop --title=filestore",
+    "dev": "egg-bin dev",
+    "debug": "egg-bin debug",
+    "test": "npm run lint -- --fix && npm run test-local",
+    "test-local": "egg-bin test",
+    "cov": "egg-bin cov",
+    "lint": "eslint .",
+    "ci": "npm run lint && npm run cov",
+    "autod": "autod",
+    "pm2": "pm2 start",
+    "restart": "pm2 restart filestore"
+  },
+  "ci": {
+    "version": "8"
+  },
+  "repository": {
+    "type": "git",
+    "url": ""
+  },
+  "author": "dyg",
+  "license": "MIT"
+}

+ 9 - 0
server.js

@@ -0,0 +1,9 @@
+
+// eslint-disable-next-line strict
+const egg = require('egg');
+
+const workers = Number(process.argv[2] || require('os').cpus().length);
+egg.startCluster({
+  workers,
+  baseDir: __dirname,
+});

+ 21 - 0
test/app/controller/home.test.js

@@ -0,0 +1,21 @@
+'use strict';
+
+const { app, assert } = require('egg-mock/bootstrap');
+
+describe('test/app/controller/home.test.js', () => {
+
+  it('should assert', function* () {
+    const pkg = require('../../../package.json');
+    assert(app.config.keys.startsWith(pkg.name));
+
+    // const ctx = app.mockContext({});
+    // yield ctx.service.xx();
+  });
+
+  it('should GET /', () => {
+    return app.httpRequest()
+      .get('/')
+      .expect('hi, egg')
+      .expect(200);
+  });
+});