lrf 2 роки тому
коміт
f7fb55295a

+ 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

+ 29 - 0
.eslintrc.json

@@ -0,0 +1,29 @@
+{
+  "extends": "./node_modules/mwts/",
+  "ignorePatterns": [
+    "node_modules",
+    "dist",
+    "test",
+    "jest.config.js",
+    "typings"
+  ],
+  "env": {
+    "jest": true
+  },
+  "rules": {
+    "max-len": [
+      "warn",
+      {
+        "code": 10000
+      }
+    ],
+    "prettier/prettier": [
+      "warn",
+      {
+        "singleQuote": true,
+        "bracketSpacing": true,
+        "printWidth": 160
+      }
+    ]
+  }
+}

+ 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')
+}

+ 9 - 0
README.md

@@ -0,0 +1,9 @@
+# midway学习
+
+## 关于封装
+
+* controller因为使用装饰器太多,但是目前并不支持装饰器继承.所以controller需要自己写
+* service封装基类:就是curd;如果要改;直接函数重载
+* 目前 接口接收参数 与 接口返回参数 是必须要自己写 class 配 接口文档装饰器的
+* 目前没想到框架要怎么封装,只是单纯的使用基类和工具类而已
+

+ 6 - 0
README.zh-CN.md

@@ -0,0 +1,6 @@
+# Bug
+
+> 如果出现 `Error: Cannot find module 'ts-node/register'`
+重新安装关于typescript依赖
+>```npm i typescript -D```  
+`npm i ts-node -D`

+ 2 - 0
bootstrap.js

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

+ 6 - 0
jest.config.js

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

+ 59 - 0
package.json

@@ -0,0 +1,59 @@
+{
+  "name": "my-midway-project",
+  "version": "1.0.0",
+  "description": "",
+  "private": true,
+  "dependencies": {
+    "@midwayjs/bootstrap": "^3.0.0",
+    "@midwayjs/core": "^3.0.0",
+    "@midwayjs/decorator": "^3.0.0",
+    "@midwayjs/info": "^3.0.0",
+    "@midwayjs/koa": "^3.0.0",
+    "@midwayjs/logger": "^2.14.0",
+    "@midwayjs/swagger": "^3.7.1",
+    "@midwayjs/typegoose": "^3.0.0",
+    "@midwayjs/validate": "^3.0.0",
+    "@typegoose/typegoose": "^9.0.0",
+    "free-midway-component": "^1.0.9",
+    "lodash": "^4.17.21",
+    "mongoose": "^6.0.7",
+    "swagger-ui-dist": "^4.15.2"
+  },
+  "devDependencies": {
+    "@midwayjs/cli": "^1.2.90",
+    "@midwayjs/mock": "^3.0.0",
+    "@types/jest": "^26.0.10",
+    "@types/koa": "^2.13.4",
+    "@types/lodash": "^4.14.190",
+    "@types/node": "14",
+    "cross-env": "^6.0.0",
+    "jest": "^26.4.0",
+    "mwts": "^1.0.5",
+    "ts-jest": "^26.2.0",
+    "ts-node": "^10.9.1",
+    "typescript": "~4.6.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"
+}

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

@@ -0,0 +1,30 @@
+import { MidwayConfig } from '@midwayjs/core';
+
+export default {
+  // use for cookie sign key, should change to your own and keep security
+  keys: '1666946133599_5072',
+  koa: {
+    port: 30000,
+    globalPrefix: '/mt',
+  },
+  swagger: {
+    swaggerPath: '/doc/api',
+  },
+  mongoose: {
+    dataSource: {
+      default: {
+        uri: 'mongodb://127.0.0.1:27017/test',
+        options: {
+          user: 'admin',
+          pass: 'admin',
+          authSource: 'admin',
+          useNewUrlParser: true,
+          // useCreateIndex: true,
+          // useFindAndModify: true,
+          // allowDiskUse: true,
+        },
+        entities: ['./entity'],
+      },
+    },
+  },
+} 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;

+ 65 - 0
src/configuration.ts

@@ -0,0 +1,65 @@
+import { Configuration, App } from '@midwayjs/decorator';
+import * as koa from '@midwayjs/koa';
+import * as validate from '@midwayjs/validate';
+import * as info from '@midwayjs/info';
+import { join } from 'path';
+// import { DefaultErrorFilter } from './filter/default.filter';
+// import { NotFoundFilter } from './filter/notfound.filter';
+import { ReportMiddleware } from './middleware/report.middleware';
+// api文档
+import * as swagger from '@midwayjs/swagger';
+// 数据库
+import * as typegoose from '@midwayjs/typegoose';
+import * as Typegoose from '@typegoose/typegoose';
+import { IMidwayApplication, IMidwayContainer } from '@midwayjs/core';
+
+// import * as freeFrame from '../../free-midway-component/dist';
+// import * as freeFrame from 'free-midway-component';
+
+import { ResponseMiddleware } from './middleware/response.middleware';
+
+@Configuration({
+  imports: [
+    // freeFrame,
+    koa,
+    validate,
+    typegoose,
+    {
+      component: swagger,
+      enabledEnvironment: ['local'],
+    },
+    {
+      component: info,
+      enabledEnvironment: ['local'],
+    },
+  ],
+  importConfigs: [join(__dirname, './config')],
+})
+export class ContainerLifeCycle {
+  @App()
+  app: koa.Application;
+
+  getApiUrl() {
+    const path = this.app.getConfig()?.swagger?.swaggerPath;
+    const port = this.app.getConfig()?.koa?.port;
+    if (path) console.log(`api文档: http://127.0.0.1:${port}${path}/index.html`);
+  }
+
+  async onReady(container: IMidwayContainer, app: IMidwayApplication) {
+    // add middleware
+    app.useMiddleware([ReportMiddleware]);
+    app.getMiddleware().insertLast(ResponseMiddleware);
+    // add filter
+    // this.app.useFilter([NotFoundFilter, DefaultErrorFilter]);
+    // typegoose
+    Typegoose.setGlobalOptions({
+      schemaOptions: {
+        id: true,
+        toJSON: { getters: true, virtuals: true },
+        toObject: { getters: true, virtuals: true },
+      },
+      options: { allowMixed: Typegoose.Severity.ALLOW },
+    });
+    this.getApiUrl();
+  }
+}

+ 18 - 0
src/controller/api.controller.ts

@@ -0,0 +1,18 @@
+import { Inject, Controller, Get, Query } from '@midwayjs/decorator';
+import { Context } from '@midwayjs/koa';
+import { UserService } from '../service/user.service';
+
+@Controller('/api')
+export class APIController {
+  @Inject()
+  ctx: Context;
+
+  @Inject()
+  userService: UserService;
+
+  @Get('/get_user')
+  async getUser(@Query('uid') uid) {
+    // const user = await this.userService.getUser({ uid });
+    // return { success: true, message: 'OK', data: user };
+  }
+}

+ 8 - 0
src/controller/home.controller.ts

@@ -0,0 +1,8 @@
+import { Controller, Get } from '@midwayjs/decorator';
+@Controller('/')
+export class HomeController {
+  @Get('/')
+  async home(): Promise<string> {
+    return 'Hello Midwayjs!';
+  }
+}

+ 54 - 0
src/controller/test/test.controller.ts

@@ -0,0 +1,54 @@
+import {
+  Body,
+  Controller,
+  Get,
+  Inject,
+  Param,
+  Post,
+  Query,
+} from '@midwayjs/core';
+import { ApiResponse } from '@midwayjs/swagger';
+import { Validate } from '@midwayjs/validate';
+import { UserDTO, FetchDTO, FetchVO, UserUpdateDTO } from '../../interface';
+import { UserService } from '../../service/user.service';
+import { BaseController } from '../../util/BaseController';
+
+@Controller('/test')
+export class TestController extends BaseController {
+  cla = 'test';
+
+  async test() {
+    console.log('test2');
+  }
+  @Inject()
+  userService: UserService;
+
+  @Get('/')
+  async query(@Query('skip') skip: number, @Query('limit') limit: number) {
+    const data = await this.userService.query({}, { skip, limit });
+    const total = await this.userService.count({});
+    return { data, total };
+  }
+
+  @Get('/:id')
+  @ApiResponse({
+    type: FetchVO,
+  })
+  async fetch(@Param('id') id: string) {
+    const data = await this.userService.fetch(id);
+    const result = new FetchVO(data);
+    return result;
+  }
+
+  @Post('/')
+  @Validate()
+  async create(@Body() data: UserDTO) {
+    const d2 = await this.userService.create(data);
+    return d2;
+  }
+  @Post('/:id')
+  async update(@Param('id') id: string, @Body() body: UserUpdateDTO) {
+    const result = await this.userService.updateOne(id, body);
+    return result;
+  }
+}

+ 33 - 0
src/entity/order.ts

@@ -0,0 +1,33 @@
+import { modelOptions, prop, Severity } from '@typegoose/typegoose';
+@modelOptions({
+  schemaOptions: { collection: 'order' },
+  options: { allowMixed: Severity.ALLOW },
+})
+export class Order {
+  @prop()
+  customer: string;
+
+  @prop()
+  address: string;
+
+  @prop()
+  total_detail: object;
+
+  @prop()
+  buy_time: string;
+
+  @prop()
+  no: string;
+
+  @prop()
+  status: string;
+
+  @prop()
+  pay: object;
+
+  @prop()
+  type: string;
+
+  @prop()
+  inviter: string;
+}

+ 20 - 0
src/entity/user.ts

@@ -0,0 +1,20 @@
+import { modelOptions, prop } from '@typegoose/typegoose';
+import { Decimal128 } from 'mongoose';
+// import * as meta from '../plugins/meta'; plugin Severity
+import { ModelBase } from '../plugins/modelBase';
+@modelOptions({
+  schemaOptions: { collection: 'user' },
+})
+export class User extends ModelBase {
+  @prop()
+  name: string;
+
+  @prop()
+  phone: string;
+
+  @prop()
+  openid: string;
+
+  @prop({ default: 1.01 })
+  money: Decimal128;
+}

+ 31 - 0
src/error/service.error.ts

@@ -0,0 +1,31 @@
+import { MidwayError, registerErrorCode } from '@midwayjs/core';
+
+export enum ErrorCode {
+  /** 未知错误 */
+  UNKNOWN = '-1',
+  /** 缺少地址参数 */
+  NEED_PARAMS = '-2',
+  /** 缺少地址后参数 */
+  NEED_QUERY = '-3',
+  /** 缺少body参数 */
+  NEED_BODY = '-4',
+  /** 未找到数据 */
+  NOT_FOUND_DATA = '-10',
+  /** 业务错误 */
+  SERVICE_FAULT = '-100',
+  /** 用户没有登录 */
+  NOT_LOGIN = '-101',
+}
+
+/** 自定义错误枚举数据 */
+export const FrameworkErrorEnum = registerErrorCode('ServiceError', ErrorCode);
+/**
+ * @constructor 自定义异常类
+ * @param {string} str 异常说明
+ * @param {ErrorCode} errcode 错误代码
+ */
+export class ServiceError extends MidwayError {
+  constructor(str: string, errcode: string = ErrorCode.UNKNOWN) {
+    super(str, errcode);
+  }
+}

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

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

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

@@ -0,0 +1,11 @@
+import { Catch } from '@midwayjs/decorator';
+import { 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');
+  }
+}

+ 7 - 0
src/interface.ts

@@ -0,0 +1,7 @@
+/**
+ * DTO:接口对象:接口接收的对象
+ * DO:业务对象:通过DTO转换而来
+ * VO:视图对象:用来展示返回
+ * PO:持久化对象:数据库使用的对象
+ */
+export * from './interface/user';

+ 71 - 0
src/interface/user.ts

@@ -0,0 +1,71 @@
+import { Rule, RuleType } from '@midwayjs/validate';
+import { ApiProperty } from '@midwayjs/swagger';
+import _ = require('lodash');
+export class UserDTO {
+  @ApiProperty({ example: '123', description: '名称' })
+  @Rule(RuleType.string().required())
+  name: string;
+
+  @ApiProperty({
+    example: '13089419810',
+    type: 'string',
+    description: '电话号码',
+  })
+  @Rule(RuleType.string().required().error(new Error('电话号码不能为空')))
+  phone: string;
+
+  @ApiProperty({
+    example: '1203a-soeps',
+    type: 'string',
+    description: '微信openid',
+  })
+  @Rule(RuleType.string().allow())
+  openid: string;
+}
+
+export class UserUpdateDTO {
+  @ApiProperty({ example: '1234', description: '名称' })
+  @Rule(RuleType.string().required())
+  name: string;
+
+  @ApiProperty({
+    example: '13089419811',
+    type: 'string',
+    description: '电话号码',
+  })
+  @Rule(RuleType.string().required().error(new Error('电话号码不能为空')))
+  phone: string;
+
+  @ApiProperty({
+    example: '12034a-soeps',
+    type: 'string',
+    description: '微信openid',
+  })
+  @Rule(RuleType.string().allow())
+  openid: string;
+}
+
+export interface QueryVO {
+  name: string;
+  phone: string;
+  openid: string;
+}
+
+export class FetchDTO {
+  @ApiProperty({ example: 'ObjectId', description: '数据id' })
+  id: string;
+}
+
+export class FetchVO {
+  constructor(data: object) {
+    this.id = _.get(data, '_id');
+    this.name = _.get(data, 'name');
+    this.phone = _.get(data, 'phone');
+  }
+  @ApiProperty({ description: 'id' })
+  id: string;
+  @ApiProperty({ description: '名称' })
+  name: string;
+  @ApiProperty({ description: '电话' })
+  phone: string;
+}

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

@@ -0,0 +1,28 @@
+import { IMiddleware } from '@midwayjs/core';
+import { Middleware } from '@midwayjs/decorator';
+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';
+  }
+}

+ 21 - 0
src/middleware/response.middleware.ts

@@ -0,0 +1,21 @@
+import { IMiddleware } from '@midwayjs/core';
+import { Middleware } from '@midwayjs/decorator';
+import { NextFunction, Context } from '@midwayjs/koa';
+import _ = require('lodash');
+import { VOBase } from '../util/VOBase';
+@Middleware()
+export class ResponseMiddleware implements IMiddleware<Context, NextFunction> {
+  resolve() {
+    return async (ctx: Context, next: NextFunction) => {
+      await next();
+      const response = ctx.response;
+      const body = response.body;
+      const nb = new VOBase(body as object);
+      return nb;
+    };
+  }
+
+  static getName(): string {
+    return 'response';
+  }
+}

+ 14 - 0
src/plugins/meta.js

@@ -0,0 +1,14 @@
+'use strict';
+
+module.exports = exports = function metaPlugin(schema /* , options*/) {
+  schema.add({
+    meta: {
+      state: { type: Number, default: 0 }, // 数据状态: 0-正常;1-标记删除
+      comment: String,
+    },
+  });
+  schema.set('timestamps', {
+    createdAt: 'meta.createdAt',
+    updatedAt: 'meta.updatedAt',
+  });
+};

+ 7 - 0
src/plugins/modelBase.ts

@@ -0,0 +1,7 @@
+import { modelOptions, plugin, Severity } from '@typegoose/typegoose';
+import * as meta from '../plugins/meta';
+@modelOptions({
+  options: { allowMixed: Severity.ALLOW },
+})
+@plugin(meta)
+export class ModelBase {}

+ 11 - 0
src/service/user.service.ts

@@ -0,0 +1,11 @@
+import { Provide } from '@midwayjs/decorator';
+import { User } from '../entity/user';
+import { InjectEntityModel } from '@midwayjs/typegoose';
+import { ReturnModelType } from '@typegoose/typegoose';
+import { BaseService } from '../util/BaseService';
+type modelType = ReturnModelType<typeof User>;
+@Provide()
+export class UserService extends BaseService<modelType> {
+  @InjectEntityModel(User)
+  model: modelType;
+}

+ 8 - 0
src/util/BaseController.ts

@@ -0,0 +1,8 @@
+import { Application, Context } from '@midwayjs/koa';
+import { App, Inject } from '@midwayjs/decorator';
+export class BaseController {
+  @App()
+  app: Application;
+  @Inject()
+  ctx: Context;
+}

+ 58 - 0
src/util/BaseService.ts

@@ -0,0 +1,58 @@
+import { ReturnModelType } from '@typegoose/typegoose';
+import { AnyParamConstructor } from '@typegoose/typegoose/lib/types';
+import { Application, Context } from '@midwayjs/koa';
+import { App, Inject } from '@midwayjs/decorator';
+import { FilterDTO } from './BaseTypes';
+import _ = require('lodash');
+import { FrameworkErrorEnum, ServiceError } from '../error/service.error';
+/**
+ * Service基类
+ */
+export abstract class BaseService<T extends AnyParamConstructor<any>> {
+  @App()
+  app: Application;
+  @Inject()
+  ctx: Context;
+
+  // 要求继承基类的service,必须给上默认的model
+  abstract model: ReturnModelType<T>;
+
+  /**
+   * 常规列表查询
+   * @param {FilterDTO} filter 查询条件
+   * @param {object} pageOptions 分页条件
+   * @param {Boolean} lean 是否使用JavaScript形式数据;false为mongoose的模型实例数据
+   * @returns {Promise<object>} 返回列表
+   */
+  async query(filter: FilterDTO = {}, pageOptions: object = {}, lean = true): Promise<object> {
+    const dup = _.cloneDeep(filter);
+    const data = await this.model.find(dup, {}, { ...pageOptions }).lean(lean);
+    return data;
+  }
+
+  async count(filter: FilterDTO = {}) {
+    const dup = _.cloneDeep(filter);
+    const total = await this.model.count(dup);
+    return total;
+  }
+
+  async fetch(id: string, lean = true) {
+    const data = await this.model.findById(id).lean(lean);
+    return data;
+  }
+
+  async updateOne(id: string, body: object) {
+    if (!id) throw new ServiceError('缺少查询信息', FrameworkErrorEnum.NEED_PARAMS);
+    const num = await this.model.count({ _id: id });
+    if (num <= 0) throw new ServiceError('未找到要修改的数据', FrameworkErrorEnum.NOT_FOUND_DATA);
+    const data = await this.model.updateOne({ _id: id }, body);
+    return data;
+  }
+
+  async delete(filter: FilterDTO) {}
+
+  async create(body: object) {
+    const data = await this.model.create(body);
+    return data;
+  }
+}

+ 3 - 0
src/util/BaseTypes.ts

@@ -0,0 +1,3 @@
+export interface FilterDTO {
+  id?: string;
+}

+ 20 - 0
src/util/VOBase.ts

@@ -0,0 +1,20 @@
+import _ = require('lodash');
+export class VOBase {
+  constructor(response: object) {
+    this.errcode = _.get(response, 'errcode', 0);
+    this.errmsg = _.get(response, 'errmsg', 'ok');
+    this.details = _.get(response, 'details');
+    this.total = _.get(response, 'total');
+    // 查询列表和只返回结果的区分,根据total决定
+    if (this.total) {
+      this.data = _.get(response, 'data');
+    } else {
+      this.data = response;
+    }
+  }
+  errcode: number;
+  errmsg: string;
+  details?: string;
+  data?: any;
+  total?: number;
+}

+ 63 - 0
src/util/util.ts

@@ -0,0 +1,63 @@
+import * as mongoose from 'mongoose';
+import * as _ from 'lodash';
+const ObjectId = mongoose.Types.ObjectId;
+export function turnFilter(filter) {
+  const str = /^%\S*%$/;
+  // $是mongodb固定条件,不用处理;大多为手写特殊处理过的条件
+  let keys = Object.keys(filter).filter(f => !f.includes('$'));
+  for (const key of keys) {
+    const res = key.match(str);
+    if (res) {
+      const newKey = key.slice(1, key.length - 1);
+      if (!ObjectId.isValid(filter[key])) filter[newKey] = new RegExp(filter[key]);
+      delete filter[key];
+    }
+  }
+  // 再次过滤数据,将数组的数据都变成{$in:value},因为查询变成了聚合查询
+  keys = Object.keys(filter).filter(f => !f.includes('$'));
+  for (const key of keys) {
+    if (_.isArray(filter[key])) {
+      filter[key] = { $in: filter[key] };
+    } else if (filter[key] === 'true' || filter[key] === 'false') {
+      // 布尔类型的值检查,如果是布尔类型,则将字符串转为布尔
+      filter[key] = filter[key] === 'true';
+    }
+  }
+  return filter;
+}
+export function turnDateRangeQuery(filter) {
+  const keys = Object.keys(filter);
+  const times = [];
+  for (const k of keys) {
+    if (k.includes('@')) {
+      const karr = k.split('@');
+      if (karr.length === 2) {
+        const prefix = karr[0];
+        const type = karr[1];
+        if (type === 'start') {
+          if (filter[k] && filter[k] !== '') {
+            const obj = { key: prefix, opera: '$gte', value: filter[k] };
+            times.push(obj);
+          }
+        } else {
+          if (filter[k] && filter[k] !== '') {
+            const obj = { key: prefix, opera: '$lte', value: filter[k] };
+            times.push(obj);
+          }
+        }
+        delete filter[k];
+      }
+    }
+  }
+  for (const i of times) {
+    const { key, opera, value } = i;
+    if (!filter[key]) {
+      filter[key] = {};
+    }
+    filter[key][opera] = value;
+  }
+  return filter;
+}
+export function dealFilter(filter) {
+  return turnFilter(turnDateRangeQuery(filter));
+}

+ 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": false,
+    "noUnusedLocals": false,
+    "stripInternal": true,
+    "skipLibCheck": true,
+    "pretty": true,
+    "declaration": true,
+    "forceConsistentCasingInFileNames": true,
+    "typeRoots": [ "./typings", "./node_modules/@types"],
+    "outDir": "dist"
+  },
+  "exclude": [
+    "dist",
+    "node_modules",
+    "test"
+  ]
+}