zs 1 년 전
커밋
4b1d29e727

+ 11 - 0
.editorconfig

@@ -0,0 +1,11 @@
+# 🎨 editorconfig.org
+
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_style = space
+indent_size = 2
+trim_trailing_whitespace = true
+insert_final_newline = true

+ 7 - 0
.eslintrc.json

@@ -0,0 +1,7 @@
+{
+  "extends": "./node_modules/mwts/",
+  "ignorePatterns": ["node_modules", "dist", "test", "jest.config.js", "typings"],
+  "env": {
+    "jest": true
+  }
+}

+ 15 - 0
.gitignore

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

+ 3 - 0
.prettierrc.js

@@ -0,0 +1,3 @@
+module.exports = {
+  ...require('mwts/.prettierrc.json')
+}

+ 29 - 0
README.md

@@ -0,0 +1,29 @@
+# my_midway_project
+
+## QuickStart
+
+<!-- add docs here for user -->
+
+see [midway docs][midway] for more detail.
+
+### Development
+
+```bash
+$ npm i
+$ npm run dev
+$ open http://localhost:7001/
+```
+
+### Deploy
+
+```bash
+$ npm start
+```
+
+### npm scripts
+
+- Use `npm run lint` to check code style.
+- Use `npm test` to run unit test.
+
+
+[midway]: https://midwayjs.org

+ 29 - 0
README.zh-CN.md

@@ -0,0 +1,29 @@
+# my_midway_project
+
+## 快速入门
+
+<!-- 在此次添加使用文档 -->
+
+如需进一步了解,参见 [midway 文档][midway]。
+
+### 本地开发
+
+```bash
+$ npm i
+$ npm run dev
+$ open http://localhost:7001/
+```
+
+### 部署
+
+```bash
+$ npm start
+```
+
+### 内置指令
+
+- 使用 `npm run lint` 来做代码风格检查。
+- 使用 `npm test` 来执行单元测试。
+
+
+[midway]: https://midwayjs.org

+ 2 - 0
bootstrap.js

@@ -0,0 +1,2 @@
+const { Bootstrap } = require('@midwayjs/bootstrap');
+Bootstrap.run();

+ 20 - 0
ecosystem.config.js

@@ -0,0 +1,20 @@
+'use strict';
+// 开发服务设置
+const app = '上传图片vue3-服务';
+module.exports = {
+  apps: [
+    {
+      name: app, // 应用名称
+      script: './bootstrap.js', // 实际启动脚本
+      out: `./logs/${app}.log`,
+      error: `./logs/${app}.err`,
+      watch: [
+        // 监控变化的目录,一旦变化,自动重启
+        'dist',
+      ],
+      env: {
+        NODE_ENV: 'production', // 环境参数,当前指定为生产环境
+      },
+    },
+  ],
+};

+ 6 - 0
jest.config.js

@@ -0,0 +1,6 @@
+module.exports = {
+  preset: 'ts-jest',
+  testEnvironment: 'node',
+  testPathIgnorePatterns: ['<rootDir>/test/fixtures'],
+  coveragePathIgnorePatterns: ['<rootDir>/test/'],
+};

+ 76 - 0
package.json

@@ -0,0 +1,76 @@
+{
+  "name": "my-midway-project",
+  "version": "1.0.0",
+  "description": "",
+  "private": true,
+  "dependencies": {
+    "@cool-midway/file": "^6.0.2",
+    "@midwayjs/axios": "^3.11.11",
+    "@midwayjs/bootstrap": "^3.0.0",
+    "@midwayjs/bull": "^3.11.11",
+    "@midwayjs/core": "^3.0.0",
+    "@midwayjs/decorator": "^3.0.0",
+    "@midwayjs/info": "^3.0.0",
+    "@midwayjs/jwt": "^3.11.11",
+    "@midwayjs/koa": "^3.0.0",
+    "@midwayjs/logger": "^2.14.0",
+    "@midwayjs/redis": "^3.11.11",
+    "@midwayjs/swagger": "^3.11.11",
+    "@midwayjs/task": "^3.6.0",
+    "@midwayjs/typegoose": "^3.11.11",
+    "@midwayjs/upload": "^3.11.11",
+    "@midwayjs/validate": "^3.0.0",
+    "@midwayjs/web": "^3.11.11",
+    "@typegoose/typegoose": "^11.3.0",
+    "amqp-connection-manager": "^4.1.13",
+    "amqplib": "^0.10.3",
+    "await-stream-ready": "^1.0.1",
+    "dayjs": "^1.11.8",
+    "exceljs": "^4.3.0",
+    "free-midway-component": "^1.0.35",
+    "midway-schedule": "^2.15.0",
+    "moment": "^2.29.4",
+    "mongoose": "^7.3.0",
+    "sharp": "^0.32.1",
+    "stream-wormhole": "^1.1.0",
+    "swagger-ui-dist": "^5.1.0"
+  },
+  "devDependencies": {
+    "@midwayjs/cli": "^2.0.0",
+    "@midwayjs/mock": "^3.0.0",
+    "@types/amqplib": "^0.10.1",
+    "@types/jest": "^29.2.0",
+    "@types/jsonwebtoken": "^9.0.2",
+    "@types/koa": "^2.13.4",
+    "@types/lodash": "^4.14.195",
+    "@types/node": "14",
+    "cross-env": "^6.0.0",
+    "jest": "^29.2.2",
+    "mwts": "^1.0.5",
+    "ts-jest": "^29.0.3",
+    "typescript": "~4.8.0"
+  },
+  "engines": {
+    "node": ">=12.0.0"
+  },
+  "scripts": {
+    "start": "NODE_ENV=production node ./bootstrap.js",
+    "dev": "cross-env NODE_ENV=local midway-bin dev --ts",
+    "test": "midway-bin test --ts",
+    "cov": "midway-bin cov --ts",
+    "lint": "mwts check",
+    "lint:fix": "mwts fix",
+    "ci": "npm run cov",
+    "build": "midway-bin build -c"
+  },
+  "midway-bin-clean": [
+    ".vscode/.tsbuildinfo",
+    "dist"
+  ],
+  "repository": {
+    "type": "git",
+    "url": ""
+  },
+  "author": "anonymous",
+  "license": "MIT"
+}

+ 59 - 0
src/config/config.default.ts

@@ -0,0 +1,59 @@
+import { MidwayConfig } from '@midwayjs/core';
+import { uploadWhiteList } from '@midwayjs/upload';
+import { tmpdir } from 'os';
+import { join } from 'path';
+const project = 'file';
+export default {
+  // use for cookie sign key, should change to your own and keep security
+  keys: '1672292154640_488',
+  koa: {
+    port: 13006,
+  },
+  jwt: {
+    secret: 'Ziyouyanfa!@#',
+    expiresIn: '2d',
+  },
+  redis_timeout: 300, //s
+  emailConfig: project,
+  axios: {
+    clients: {
+      Axios: {
+        baseURL: 'https://www.ccwit.net',
+        //表明返回服务器返回的数据类型
+        responseType: 'arraybuffer',
+        headers: {
+          'Content-Type': 'application/json; application/octet-stream',
+        },
+      },
+    },
+  },
+  export: {
+    root_path: 'D:\\temp',
+    file_type: 'friendSchool',
+  },
+  wechatSetting: {
+    // 吉林大学汽车工程学院校友会
+    friendShoolApp: {
+      appid: 'wxef5b87d5fef241b4',
+      secret: 'd315753502e0cbc24de5a230bed5c87a',
+      mchid: '1505364491',
+      v3key: '1qaz2wsx3edc4rfv5tgb6yhn7ujm8ik9',
+    },
+  },
+  upload: {
+    // mode: UploadMode, 默认为file,即上传到服务器临时目录,可以配置为 stream
+    mode: 'file',
+    // fileSize: string, 最大上传文件大小,默认为 10mb
+    fileSize: '10mb',
+    // whitelist: string[],文件扩展名白名单
+    whitelist: uploadWhiteList.filter(ext => ext !== '.pdf'),
+    // tmpdir: string,上传的文件临时存储路径
+    tmpdir: join(tmpdir(), 'midway-upload-files'),
+    // cleanTimeout: number,上传的文件在临时目录中多久之后自动删除,默认为 5 分钟
+    cleanTimeout: 5 * 60 * 1000,
+    // base64: boolean,设置原始body是否是base64格式,默认为false,一般用于腾讯云的兼容
+    base64: false,
+    // 仅在匹配路径到 /api/upload 的时候去解析 body 中的文件信息
+    match: /\/api\/upload/,
+  },
+} as MidwayConfig;

+ 63 - 0
src/config/config.local.ts

@@ -0,0 +1,63 @@
+import { MidwayConfig } from '@midwayjs/core';
+const ip = '127.0.0.1';
+const project = 'file';
+const mongodb = 'school_friend_v1';
+export default {
+  // use for cookie sign key, should change to your own and keep security
+  keys: '1672292154640_488',
+  koa: {
+    globalPrefix: `/${project}`,
+  },
+  swagger: {
+    swaggerPath: `/dev/${project}/v1/api/doc`,
+  },
+  mongoose: {
+    dataSource: {
+      default: {
+        uri: `mongodb://${ip}:27017/${mongodb}`,
+        options: {
+          user: 'admin',
+          pass: 'admin',
+          authSource: 'admin',
+          useNewUrlParser: true,
+        },
+        entities: ['./entity'],
+      },
+    },
+  },
+  bull: {
+    // defaultQueueOptions: {
+    //   redis: {
+    //     port: 6379,
+    //     host: '114.215.24.90',
+    //     password: '123456',
+    //   },
+    //   prefix: '{midway-bull}',
+    // },
+  },
+  axios: {
+    clients: {
+      file: {
+        baseURL: 'https://www.ccwit.net',
+        //表明返回服务器返回的数据类型
+        responseType: 'arraybuffer',
+        headers: {
+          'Content-Type': 'application/json; application/octet-stream',
+        },
+      },
+    },
+  },
+  export: {
+    root_path: 'D:\\temp',
+    file_type: 'friendSchool',
+  },
+  wechatSetting: {
+    // 吉林大学汽车工程学院校友会
+    friendShoolApp: {
+      appid: 'wxef5b87d5fef241b4',
+      secret: 'd315753502e0cbc24de5a230bed5c87a',
+      mchid: '1505364491',
+      v3key: '1qaz2wsx3edc4rfv5tgb6yhn7ujm8ik9',
+    },
+  },
+} as MidwayConfig;

+ 63 - 0
src/config/config.prod.ts

@@ -0,0 +1,63 @@
+import { MidwayConfig } from '@midwayjs/core';
+const ip = '127.0.0.1';
+const project = 'file';
+const mongodb = 'school_friend_v1';
+export default {
+  // use for cookie sign key, should change to your own and keep security
+  keys: '1672292154640_488',
+  koa: {
+    globalPrefix: `/${project}/`,
+  },
+  swagger: {
+    swaggerPath: `/${project}/v1/api/doc`,
+  },
+  mongoose: {
+    dataSource: {
+      default: {
+        uri: `mongodb://${ip}:27017/${mongodb}`,
+        options: {
+          user: 'admin',
+          pass: 'admin',
+          authSource: 'admin',
+          useNewUrlParser: true,
+        },
+        entities: ['./entity'],
+      },
+    },
+  },
+  bull: {
+    defaultQueueOptions: {
+      // redis: {
+      //   port: 6379,
+      //   host: '114.215.24.90',
+      //   password: '123456',
+      // },
+      // prefix: '{midway-bull}',
+    },
+  },
+  axios: {
+    clients: {
+      file: {
+        baseURL: 'https://www.ccwit.net',
+        //表明返回服务器返回的数据类型
+        responseType: 'arraybuffer',
+        headers: {
+          'Content-Type': 'application/json; application/octet-stream',
+        },
+      },
+    },
+  },
+  export: {
+    root_path: 'D:\\free\\workspace\\server\\service-file\\upload',
+    file_type: 'friendSchool',
+  },
+  wechatSetting: {
+    // 吉林大学汽车工程学院校友会
+    friendShoolApp: {
+      appid: 'wxef5b87d5fef241b4',
+      secret: 'd315753502e0cbc24de5a230bed5c87a',
+      mchid: '1505364491',
+      v3key: '1qaz2wsx3edc4rfv5tgb6yhn7ujm8ik9',
+    },
+  },
+} as MidwayConfig;

+ 7 - 0
src/config/config.unittest.ts

@@ -0,0 +1,7 @@
+import { MidwayConfig } from '@midwayjs/core';
+
+export default {
+  koa: {
+    port: null,
+  },
+} as MidwayConfig;

+ 49 - 0
src/configuration.ts

@@ -0,0 +1,49 @@
+import { Configuration, App } from '@midwayjs/core';
+import * as koa from '@midwayjs/koa';
+import * as validate from '@midwayjs/validate';
+import * as info from '@midwayjs/info';
+import * as jwt from '@midwayjs/jwt';
+import * as redis from '@midwayjs/redis';
+import * as axios from '@midwayjs/axios';
+import * as upload from '@midwayjs/upload';
+// import { IMidwayContainer } from '@midwayjs/core';
+import { join } from 'path';
+// freemidway组件项目
+import * as FreeFrame from 'free-midway-component';
+// import { FrameworkErrorEnum, ServiceError } from 'free-midway-component';
+// 控制器执行前函数
+import { CheckTokenMiddleware } from './middleware/checkToken.middleware';
+// 请求成功,失败提示
+// const axiosResponse = response => {
+//   if (response.status === 0) return response.data;
+//   else {
+//     console.log(JSON.stringify(response));
+//     throw new ServiceError('请求失败', FrameworkErrorEnum.SERVICE_FAULT);
+//   }
+// };
+// const axiosError = error => {
+//   return Promise.reject(error);
+// };
+@Configuration({
+  imports: [
+    FreeFrame,
+    validate,
+    jwt,
+    redis,
+    axios,
+    upload,
+    {
+      component: info,
+      enabledEnvironment: ['local'],
+    },
+  ],
+  importConfigs: [join(__dirname, './config')],
+})
+export class ContainerLifeCycle {
+  @App()
+  app: koa.Application;
+
+  async onReady() {
+    this.app.getMiddleware().insertFirst(CheckTokenMiddleware);
+  }
+}

+ 16 - 0
src/controller/file.controller.ts

@@ -0,0 +1,16 @@
+import { Controller, Inject, Post, Files, Fields } from '@midwayjs/core';
+import { FileService } from '../service/file.service';
+@Controller('/api')
+export class FileController {
+  @Inject()
+  service: FileService;
+
+  @Inject()
+  ctx;
+
+  @Post('/upload')
+  async upload(@Files() files, @Fields() fields) {
+    const result = await this.service.upload(files, fields);
+    return result;
+  }
+}

+ 13 - 0
src/filter/default.filter.ts

@@ -0,0 +1,13 @@
+import { Catch } from '@midwayjs/core';
+import { Context } from '@midwayjs/koa';
+
+@Catch()
+export class DefaultErrorFilter {
+  async catch(err: Error, ctx: Context) {
+    // 所有的未分类错误会到这里
+    return {
+      success: false,
+      message: err.message,
+    };
+  }
+}

+ 10 - 0
src/filter/notfound.filter.ts

@@ -0,0 +1,10 @@
+import { Catch, httpError, MidwayHttpError } from '@midwayjs/core';
+import { Context } from '@midwayjs/koa';
+
+@Catch(httpError.NotFoundError)
+export class NotFoundFilter {
+  async catch(err: MidwayHttpError, ctx: Context) {
+    // 404 错误会到这里
+    ctx.redirect('/404.html');
+  }
+}

+ 6 - 0
src/interface.ts

@@ -0,0 +1,6 @@
+/**
+ * @description User-Service parameters
+ */
+export interface IUserOptions {
+  uid: number;
+}

+ 33 - 0
src/middleware/checkToken.middleware.ts

@@ -0,0 +1,33 @@
+import { IMiddleware } from '@midwayjs/core';
+import { Middleware, Inject } from '@midwayjs/decorator';
+import { NextFunction, Context } from '@midwayjs/koa';
+import get = require('lodash/get');
+import { JwtService } from '@midwayjs/jwt';
+
+@Middleware()
+export class CheckTokenMiddleware
+  implements IMiddleware<Context, NextFunction>
+{
+  @Inject()
+  jwtService: JwtService;
+  resolve() {
+    return async (ctx: Context, next: NextFunction) => {
+      const token: any = get(ctx.request, 'header.token');
+      if (token) {
+        const data = this.jwtService.decodeSync(token);
+        if (data) ctx.user = data;
+      }
+      // 添加管理员身份
+      const adminToken: any = get(ctx.request, 'header.admin-token');
+      if (adminToken) {
+        const data = this.jwtService.decodeSync(adminToken);
+        if (data) ctx.admin = data;
+      }
+      await next();
+    };
+  }
+
+  static getName(): string {
+    return 'checkToken';
+  }
+}

+ 27 - 0
src/middleware/report.middleware.ts

@@ -0,0 +1,27 @@
+import { Middleware, IMiddleware } from '@midwayjs/core';
+import { NextFunction, Context } from '@midwayjs/koa';
+
+@Middleware()
+export class ReportMiddleware implements IMiddleware<Context, NextFunction> {
+  resolve() {
+    return async (ctx: Context, next: NextFunction) => {
+      // 控制器前执行的逻辑
+      const startTime = Date.now();
+      // 执行下一个 Web 中间件,最后执行到控制器
+      // 这里可以拿到下一个中间件或者控制器的返回值
+      const result = await next();
+      // 控制器之后执行的逻辑
+      ctx.logger.info(
+        `Report in "src/middleware/report.middleware.ts", rt = ${
+          Date.now() - startTime
+        }ms`
+      );
+      // 返回给上一个中间件的结果
+      return result;
+    };
+  }
+
+  static getName(): string {
+    return 'report';
+  }
+}

+ 61 - 0
src/service/file.service.ts

@@ -0,0 +1,61 @@
+import { Provide, Inject } from '@midwayjs/decorator';
+import { Config, InjectClient } from '@midwayjs/core';
+import { FrameworkErrorEnum, ServiceError } from 'free-midway-component';
+import * as fs from 'node:fs';
+import * as Path from 'node:path';
+import { HttpServiceFactory, HttpService } from '@midwayjs/axios';
+const moment = require('moment');
+const sharp = require('sharp');
+const sep = Path.sep;
+@Provide()
+export class FileService {
+  @Config('export.root_path')
+  root_path;
+
+  @Config('export.file_type')
+  file_type;
+
+  @InjectClient(HttpServiceFactory, 'Axios')
+  Axios: HttpService;
+
+  @Inject()
+  ctx;
+
+  async upload(files, fields) {
+    const path = `${this.root_path}${sep}${this.file_type}`;
+    if (!path) {
+      throw new ServiceError(
+        '服务端没有设置存储路径',
+        FrameworkErrorEnum.SERVICE_FAULT
+      );
+    }
+    if (!fs.existsSync(path)) {
+      // 如果不存在文件夹,就创建
+      this.mkdir(path);
+    }
+    // TODO: 指定文件名或者按时间生成文件名
+    // const name = moment().format('YYYYMMDDHHmmss');
+    const filePath = `${path}${sep}`;
+    for (const val of files) {
+      const filename = moment().format('YYYYMMDDHHmmss');
+      await sharp(val.data)
+        .toFormat('jpg', { mozjpeg: true })
+        .toFile(`${filePath}${filename}.jpg`);
+      return {
+        id: filename,
+        name: `${filename}.jpg`,
+        uri: `/files/friendSchool/${filename}.jpg`,
+      };
+    }
+  }
+  // 创建文件夹
+  mkdir(dirname) {
+    if (fs.existsSync(dirname)) {
+      return true;
+    }
+    if (this.mkdir(Path.dirname(dirname))) {
+      fs.mkdirSync(dirname);
+      return true;
+    }
+  }
+}

+ 20 - 0
test/controller/api.test.ts

@@ -0,0 +1,20 @@
+import { createApp, close, createHttpRequest } from '@midwayjs/mock';
+import { Framework } from '@midwayjs/koa';
+
+describe('test/controller/home.test.ts', () => {
+
+  it('should POST /api/get_user', async () => {
+    // create app
+    const app = await createApp<Framework>();
+
+    // make request
+    const result = await createHttpRequest(app).get('/api/get_user').query({ uid: 123 });
+
+    // use expect by jest
+    expect(result.status).toBe(200);
+    expect(result.body.message).toBe('OK');
+
+    // close app
+    await close(app);
+  });
+});

+ 21 - 0
test/controller/home.test.ts

@@ -0,0 +1,21 @@
+import { createApp, close, createHttpRequest } from '@midwayjs/mock';
+import { Framework } from '@midwayjs/koa';
+
+describe('test/controller/home.test.ts', () => {
+
+  it('should GET /', async () => {
+    // create app
+    const app = await createApp<Framework>();
+
+    // make request
+    const result = await createHttpRequest(app).get('/');
+
+    // use expect by jest
+    expect(result.status).toBe(200);
+    expect(result.text).toBe('Hello Midwayjs!');
+
+    // close app
+    await close(app);
+  });
+
+});

+ 25 - 0
tsconfig.json

@@ -0,0 +1,25 @@
+{
+  "compileOnSave": true,
+  "compilerOptions": {
+    "target": "es2018",
+    "module": "commonjs",
+    "moduleResolution": "node",
+    "experimentalDecorators": true,
+    "emitDecoratorMetadata": true,
+    "inlineSourceMap":true,
+    "noImplicitThis": true,
+    "noUnusedLocals": true,
+    "stripInternal": true,
+    "skipLibCheck": true,
+    "pretty": true,
+    "declaration": true,
+    "forceConsistentCasingInFileNames": true,
+    "typeRoots": [ "./typings", "./node_modules/@types"],
+    "outDir": "dist"
+  },
+  "exclude": [
+    "dist",
+    "node_modules",
+    "test"
+  ]
+}