├── docs ├── extend │ ├── rabbitmq.md │ ├── workflow.md │ ├── crud.md │ ├── ms.md │ ├── event.md │ └── decorator.md ├── images │ ├── log_error.png │ ├── swagger.png │ ├── log_normal.png │ └── ws-run-test.png ├── README.md ├── config.md ├── quickstart.md ├── utils.md ├── .vuepress │ └── config.js ├── database │ └── mongoose.md ├── contribut.md └── code-style.md ├── template ├── .npmignore ├── index.js └── package.json ├── lerna.json ├── .prettierrc ├── common ├── nest-cli.json ├── .prettierrc ├── tsconfig.build.json ├── .dockerignore ├── Makefile ├── commitlint.config.js ├── tsconfig.json ├── .eslintrc.js ├── README.md └── Dockerfile ├── sample ├── nest-keycloak │ ├── config │ │ ├── production.js │ │ ├── test.js │ │ ├── dev.js │ │ └── default.js │ ├── nest-cli.json │ ├── .prettierrc │ ├── tsconfig.build.json │ ├── .dockerignore │ ├── Makefile │ ├── src │ │ ├── app.module.ts │ │ ├── users │ │ │ ├── users.module.ts │ │ │ ├── users.dto.ts │ │ │ ├── users.service.ts │ │ │ └── users.controller.ts │ │ ├── main.ts │ │ ├── settings.ts │ │ └── common │ │ │ └── filters │ │ │ └── http-exception.filter.ts │ ├── test │ │ ├── jest-e2e.json │ │ ├── users │ │ │ ├── users-mock-auth.e2e-spec.ts │ │ │ └── users-auth.e2e-spec.ts │ │ ├── test-helper.ts │ │ └── main.ts │ ├── commitlint.config.js │ ├── tsconfig.json │ ├── .eslintrc.js │ ├── README.md │ ├── Dockerfile │ └── package.json ├── redis-redlock-cache │ ├── tslint.json │ ├── nest-cli.json │ ├── .prettierrc │ ├── tsconfig.build.json │ ├── config │ │ ├── test.js │ │ └── default.js │ ├── .dockerignore │ ├── Makefile │ ├── src │ │ ├── app.module.ts │ │ ├── users │ │ │ ├── users.module.ts │ │ │ ├── users.dto.ts │ │ │ └── users.controller.ts │ │ └── main.ts │ ├── test │ │ ├── jest-e2e.json │ │ ├── main.ts │ │ ├── test-helper.ts │ │ └── users │ │ │ └── users-redis.e2e-spec.ts │ ├── commitlint.config.js │ ├── tsconfig.json │ ├── .eslintrc.js │ ├── README.md │ ├── Dockerfile │ └── package.json ├── hello-world │ ├── nest-cli.json │ ├── .prettierrc │ ├── tsconfig.build.json │ ├── .dockerignore │ ├── Makefile │ ├── src │ │ ├── settings.ts │ │ ├── users │ │ │ ├── model │ │ │ │ ├── account.model.ts │ │ │ │ └── user.model.ts │ │ │ ├── users.module.ts │ │ │ ├── users.dto.ts │ │ │ ├── users.service.ts │ │ │ ├── users.service.spec.ts │ │ │ ├── users.controller.spec.ts │ │ │ └── users.controller.ts │ │ ├── app.module.ts │ │ └── main.ts │ ├── test │ │ ├── jest-e2e.json │ │ ├── users │ │ │ ├── users-hello.e2e-spec.ts │ │ │ ├── users-find.e2e-spec.ts │ │ │ ├── users-err-handle.e2e-spec.ts │ │ │ ├── users-multi-db.e2e-spec.ts │ │ │ └── users-register.e2e-spec.ts │ │ ├── model-mock-template.ts │ │ ├── .run │ │ │ └── E2E.run.xml │ │ ├── main.ts │ │ └── test-helper.ts │ ├── config │ │ ├── test.js │ │ ├── production.js │ │ ├── dev.js │ │ └── default.js │ ├── commitlint.config.js │ ├── tsconfig.json │ ├── .eslintrc.js │ ├── README.md │ └── Dockerfile ├── nest-simple │ ├── nest-cli.json │ ├── .prettierrc │ ├── tsconfig.build.json │ ├── src │ │ ├── settings.ts │ │ ├── users │ │ │ ├── users.module.ts │ │ │ ├── users.service.ts │ │ │ ├── users.dto.ts │ │ │ └── users.controller.ts │ │ ├── main.ts │ │ └── app.module.ts │ ├── .dockerignore │ ├── Makefile │ ├── test │ │ ├── jest-e2e.json │ │ ├── main.ts │ │ ├── test-helper.ts │ │ └── users │ │ │ └── users-register.e2e-spec.ts │ ├── config │ │ ├── test.js │ │ ├── production.js │ │ ├── dev.js │ │ └── default.js │ ├── commitlint.config.js │ ├── tsconfig.json │ ├── .eslintrc.js │ ├── README.md │ ├── Dockerfile │ └── package.json ├── nest-zeebe │ ├── nest-cli.json │ ├── .prettierrc │ ├── flows │ │ └── recharge.png │ ├── test │ │ ├── hello.e2e-spec.ts │ │ ├── jest-e2e.json │ │ └── main.ts │ ├── tsconfig.build.json │ ├── src │ │ ├── util.ts │ │ ├── settings.ts │ │ ├── model │ │ │ └── order.model.ts │ │ ├── main.ts │ │ ├── app.module.ts │ │ ├── common │ │ │ └── filters │ │ │ │ └── http-exception.filter.ts │ │ └── service │ │ │ └── recharge.service.ts │ ├── .dockerignore │ ├── Makefile │ ├── config │ │ ├── dev.js │ │ ├── test.js │ │ ├── production.js │ │ └── default.js │ ├── commitlint.config.js │ ├── tsconfig.json │ ├── .eslintrc.js │ ├── README.md │ └── Dockerfile ├── nest-ms-consumer │ ├── nest-cli.json │ ├── .prettierrc │ ├── tsconfig.build.json │ ├── src │ │ ├── settings.ts │ │ ├── users │ │ │ ├── users.service.ts │ │ │ ├── users.dto.ts │ │ │ ├── users.module.ts │ │ │ └── users.controller.ts │ │ ├── main.ts │ │ └── app.module.ts │ ├── .dockerignore │ ├── Makefile │ ├── test │ │ ├── jest-e2e.json │ │ ├── main.ts │ │ ├── test-helper.ts │ │ └── users │ │ │ └── users-register.e2e-spec.ts │ ├── config │ │ ├── test.js │ │ ├── production.js │ │ ├── dev.js │ │ └── default.js │ ├── commitlint.config.js │ ├── tsconfig.json │ ├── .eslintrc.js │ ├── README.md │ └── Dockerfile └── nest-ms-provider │ ├── nest-cli.json │ ├── .prettierrc │ ├── tsconfig.build.json │ ├── src │ ├── settings.ts │ ├── users │ │ ├── users.module.ts │ │ ├── users.service.ts │ │ ├── users.dto.ts │ │ └── users.controller.ts │ ├── app.module.ts │ └── main.ts │ ├── .dockerignore │ ├── Makefile │ ├── test │ ├── jest-e2e.json │ ├── main.ts │ ├── test-helper.ts │ └── users │ │ └── users-register.e2e-spec.ts │ ├── config │ ├── test.js │ ├── production.js │ ├── dev.js │ └── default.js │ ├── commitlint.config.js │ ├── tsconfig.json │ ├── .eslintrc.js │ ├── README.md │ └── Dockerfile ├── tools └── gulp │ ├── gulpfile.ts │ ├── config.ts │ ├── util │ └── task-helpers.ts │ ├── tsconfig.json │ └── tasks │ ├── copy-misc.ts │ ├── move.ts │ ├── clean.ts │ └── packages.ts ├── packages ├── utils │ ├── src │ │ ├── DateUtil.ts │ │ ├── Logger.ts │ │ ├── LoggerFactory.ts │ │ ├── LoggerConfig.ts │ │ └── NumberUtil.ts │ ├── tsconfig.json │ ├── index.ts │ ├── .npmignore │ ├── Readme.md │ └── package.json ├── mongoose │ ├── tsconfig.build.json │ ├── src │ │ ├── objectId.ts │ │ ├── typegoose │ │ │ ├── index.ts │ │ │ ├── typegoose.decorators.ts │ │ │ ├── typegoose-class.interface.ts │ │ │ ├── typegoose.constants.ts │ │ │ ├── typegoose.utils.ts │ │ │ └── typegoose-options.interface.ts │ │ ├── typegoose.module.builder.ts │ │ ├── config.parse.ts │ │ └── test.database.helper.ts │ ├── tsconfig.json │ ├── .npmignore │ ├── test │ │ └── jest-e2e.json │ ├── Readme.md │ ├── index.ts │ └── package.json ├── keycloak │ ├── src │ │ ├── constants.ts │ │ ├── decorators │ │ │ └── roles.decorator.ts │ │ ├── interface │ │ │ └── keycloak-connect-options.interface.ts │ │ ├── keycloak.module.ts │ │ └── guards │ │ │ └── auth.guard.ts │ ├── tsconfig.json │ ├── .npmignore │ ├── Readme.md │ ├── index.ts │ └── package.json ├── redis │ ├── tsconfig.json │ ├── .npmignore │ ├── src │ │ ├── redlock │ │ │ ├── redlock.constants.ts │ │ │ ├── redlock.interface.ts │ │ │ ├── redlock.service.ts │ │ │ ├── redlock.module.ts │ │ │ └── redlock.ts │ │ ├── config.parse.ts │ │ └── ioredis │ │ │ └── redis.module.builder.ts │ ├── Readme.md │ ├── index.ts │ └── package.json ├── web │ ├── tsconfig.json │ ├── .npmignore │ ├── src │ │ ├── base.response.ts │ │ ├── exceptions │ │ │ └── business.exception.ts │ │ ├── transform.interceptor.ts │ │ ├── validation.pipe.ts │ │ └── http.exception.filter.ts │ ├── index.ts │ ├── Readme.md │ └── package.json ├── .npmignore ├── Readme.md └── tsconfig.base.json ├── run-tests.sh ├── .npmignore ├── upgrade-packages.sh ├── .gitignore ├── commitlint.config.js ├── gulpfile.js ├── tsconfig.json ├── deploy_doc.sh ├── .eslintrc.js └── README.md /docs/extend/rabbitmq.md: -------------------------------------------------------------------------------- 1 | ## RabbitMQ(TODO) 2 | -------------------------------------------------------------------------------- /template/.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | temp/ 3 | dist 4 | .idea/ 5 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "version": "1.0.4" 6 | } 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "none" 5 | } 6 | -------------------------------------------------------------------------------- /common/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /common/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "none" 5 | } 6 | -------------------------------------------------------------------------------- /sample/nest-keycloak/config/production.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: process.env.PORT || 3000 3 | } 4 | -------------------------------------------------------------------------------- /sample/redis-redlock-cache/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint-config-klg" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /docs/images/log_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaolalicai/klg-nest-starter/HEAD/docs/images/log_error.png -------------------------------------------------------------------------------- /docs/images/swagger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaolalicai/klg-nest-starter/HEAD/docs/images/swagger.png -------------------------------------------------------------------------------- /sample/hello-world/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /sample/nest-simple/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /sample/nest-zeebe/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /docs/images/log_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaolalicai/klg-nest-starter/HEAD/docs/images/log_normal.png -------------------------------------------------------------------------------- /docs/images/ws-run-test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaolalicai/klg-nest-starter/HEAD/docs/images/ws-run-test.png -------------------------------------------------------------------------------- /sample/nest-keycloak/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /sample/nest-ms-consumer/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /sample/nest-ms-provider/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /sample/hello-world/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "none" 5 | } 6 | -------------------------------------------------------------------------------- /sample/nest-keycloak/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "none" 5 | } 6 | -------------------------------------------------------------------------------- /sample/nest-simple/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "none" 5 | } 6 | -------------------------------------------------------------------------------- /sample/nest-zeebe/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "none" 5 | } 6 | -------------------------------------------------------------------------------- /sample/redis-redlock-cache/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /sample/nest-ms-consumer/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "none" 5 | } 6 | -------------------------------------------------------------------------------- /sample/nest-ms-provider/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "none" 5 | } 6 | -------------------------------------------------------------------------------- /sample/redis-redlock-cache/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "none" 5 | } 6 | -------------------------------------------------------------------------------- /sample/nest-zeebe/flows/recharge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaolalicai/klg-nest-starter/HEAD/sample/nest-zeebe/flows/recharge.png -------------------------------------------------------------------------------- /tools/gulp/gulpfile.ts: -------------------------------------------------------------------------------- 1 | import './tasks/copy-misc' 2 | import './tasks/clean' 3 | import './tasks/packages' 4 | import './tasks/move' 5 | -------------------------------------------------------------------------------- /common/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/utils/src/DateUtil.ts: -------------------------------------------------------------------------------- 1 | import { DateUtil as DateUtilOrigin } from 'klg-date' 2 | 3 | export class DateUtil extends DateUtilOrigin {} 4 | -------------------------------------------------------------------------------- /sample/nest-zeebe/test/hello.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | describe('AppController (e2e) ', () => { 2 | it('hello', () => { 3 | // empty 4 | }) 5 | }) 6 | -------------------------------------------------------------------------------- /docs/extend/workflow.md: -------------------------------------------------------------------------------- 1 | 关于工作流的作用、选择、以及在 Nest 中落地,请看这篇调研报告 [使用工作流处理复杂业务流程](https://www.yuque.com/docs/share/a1be1b26-cb51-4442-8d6e-053a355da7aa?#) 2 | -------------------------------------------------------------------------------- /packages/mongoose/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /sample/hello-world/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /sample/nest-keycloak/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /sample/nest-simple/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /sample/nest-zeebe/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /sample/nest-ms-consumer/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /sample/nest-ms-provider/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/utils/src/Logger.ts: -------------------------------------------------------------------------------- 1 | import { LogFactory } from './LoggerFactory' 2 | 3 | // 默认提供的 logger 4 | const logger = LogFactory() 5 | 6 | export { logger } 7 | -------------------------------------------------------------------------------- /run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | for d in sample/*/ ; do 4 | echo "$d" 5 | cd "$d" 6 | npm run test:e2e || exit $? 7 | cd ../../ 8 | done 9 | -------------------------------------------------------------------------------- /sample/redis-redlock-cache/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/keycloak/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const KEYCLOAK_CONNECT_OPTIONS = 'KEYCLOAK_CONNECT_OPTIONS' 2 | 3 | export const KEYCLOAK_INSTANCE = 'KEYCLOAK_INSTANCE' 4 | -------------------------------------------------------------------------------- /sample/nest-zeebe/src/util.ts: -------------------------------------------------------------------------------- 1 | export function revolver(rate?: number) { 2 | rate = rate || 1 / 6 3 | if (Math.random() < rate) throw new Error('发生内部错误') 4 | } 5 | -------------------------------------------------------------------------------- /packages/mongoose/src/objectId.ts: -------------------------------------------------------------------------------- 1 | import { Types } from 'mongoose' 2 | export function ObjectId(id?: string): Types.ObjectId { 3 | return new Types.ObjectId(id) 4 | } 5 | -------------------------------------------------------------------------------- /packages/keycloak/src/decorators/roles.decorator.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from '@nestjs/common' 2 | 3 | export const Roles = (...roles: string[]) => SetMetadata('roles', roles) 4 | -------------------------------------------------------------------------------- /packages/redis/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./../tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "./" 5 | }, 6 | "include": ["*.ts", "**/*.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./../tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "./" 5 | }, 6 | "include": ["*.ts", "**/*.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./../tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "./" 5 | }, 6 | "include": ["*.ts", "**/*.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/keycloak/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./../tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "./" 5 | }, 6 | "include": ["*.ts", "**/*.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/mongoose/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./../tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "./" 5 | }, 6 | "include": ["*.ts", "**/*.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /tools/gulp/config.ts: -------------------------------------------------------------------------------- 1 | // All paths are related to the base dir 2 | export const source = 'packages' 3 | export const samplePath = 'sample' 4 | export const packagesPath = 'packages' 5 | -------------------------------------------------------------------------------- /packages/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './src/DateUtil' 2 | export * from './src/Logger' 3 | export * from './src/LoggerFactory' 4 | export * from './src/NumberUtil' 5 | // for publish 2 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # source 2 | **/*.ts 3 | *.ts 4 | 5 | # definitions 6 | !**/*.d.ts 7 | !*.d.ts 8 | 9 | # configuration 10 | package-lock.json 11 | tslint.json 12 | tsconfig.json 13 | .prettierrc -------------------------------------------------------------------------------- /packages/.npmignore: -------------------------------------------------------------------------------- 1 | # source 2 | **/*.ts 3 | *.ts 4 | 5 | # definitions 6 | !**/*.d.ts 7 | !*.d.ts 8 | 9 | # configuration 10 | package-lock.json 11 | tslint.json 12 | tsconfig.json 13 | .prettierrc -------------------------------------------------------------------------------- /packages/web/.npmignore: -------------------------------------------------------------------------------- 1 | # source 2 | **/*.ts 3 | *.ts 4 | 5 | # definitions 6 | !**/*.d.ts 7 | !*.d.ts 8 | 9 | # configuration 10 | package-lock.json 11 | tslint.json 12 | tsconfig.json 13 | .prettierrc -------------------------------------------------------------------------------- /packages/keycloak/.npmignore: -------------------------------------------------------------------------------- 1 | # source 2 | **/*.ts 3 | *.ts 4 | 5 | # definitions 6 | !**/*.d.ts 7 | !*.d.ts 8 | 9 | # configuration 10 | package-lock.json 11 | tslint.json 12 | tsconfig.json 13 | .prettierrc -------------------------------------------------------------------------------- /packages/mongoose/src/typegoose/index.ts: -------------------------------------------------------------------------------- 1 | export * from './typegoose.decorators'; 2 | export * from './typegoose.module'; 3 | export * from './typegoose.utils'; 4 | export * from './typegoose-options.interface'; 5 | -------------------------------------------------------------------------------- /packages/redis/.npmignore: -------------------------------------------------------------------------------- 1 | # source 2 | **/*.ts 3 | *.ts 4 | 5 | # definitions 6 | !**/*.d.ts 7 | !*.d.ts 8 | 9 | # configuration 10 | package-lock.json 11 | tslint.json 12 | tsconfig.json 13 | .prettierrc -------------------------------------------------------------------------------- /packages/utils/.npmignore: -------------------------------------------------------------------------------- 1 | # source 2 | **/*.ts 3 | *.ts 4 | 5 | # definitions 6 | !**/*.d.ts 7 | !*.d.ts 8 | 9 | # configuration 10 | package-lock.json 11 | tslint.json 12 | tsconfig.json 13 | .prettierrc -------------------------------------------------------------------------------- /packages/mongoose/.npmignore: -------------------------------------------------------------------------------- 1 | # source 2 | **/*.ts 3 | *.ts 4 | 5 | # definitions 6 | !**/*.d.ts 7 | !*.d.ts 8 | 9 | # configuration 10 | package-lock.json 11 | tslint.json 12 | tsconfig.json 13 | .prettierrc 14 | .idea -------------------------------------------------------------------------------- /packages/web/src/base.response.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | 3 | export class BaseResponse { 4 | @ApiProperty() 5 | readonly code!: number 6 | @ApiProperty() 7 | readonly message!: string 8 | } 9 | -------------------------------------------------------------------------------- /sample/nest-simple/src/settings.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common' 2 | 3 | export const prefix = 'api/v1' 4 | 5 | export function appSettings(app: INestApplication) { 6 | app.setGlobalPrefix(prefix) 7 | } 8 | -------------------------------------------------------------------------------- /sample/nest-ms-consumer/src/settings.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common' 2 | 3 | export const prefix = 'api/v1' 4 | 5 | export function appSettings(app: INestApplication) { 6 | app.setGlobalPrefix(prefix) 7 | } 8 | -------------------------------------------------------------------------------- /sample/nest-ms-provider/src/settings.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common' 2 | 3 | export const prefix = 'api/v1' 4 | 5 | export function appSettings(app: INestApplication) { 6 | app.setGlobalPrefix(prefix) 7 | } 8 | -------------------------------------------------------------------------------- /common/.dockerignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules/ 3 | 4 | # build 5 | dist/ 6 | 7 | logs/ 8 | # tests 9 | coverage/ 10 | .nyc_output/ 11 | 12 | # IDE 13 | /.idea 14 | /.awcache 15 | /.vscode 16 | *.code-workspace 17 | 18 | -------------------------------------------------------------------------------- /sample/redis-redlock-cache/config/test.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: process.env.PORT || 3000, 3 | redis: { 4 | uri: process.env.REDIS_URI || 'redis://localhost:6379', 5 | prefix: process.env.REDIS_PREFIX || 'redis_' 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /upgrade-packages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | for d in sample/*/ ; do 4 | echo "$d" 5 | cd "$d" 6 | ncu @kalengo/keycloak @kalengo/web @kalengo/mongoose @kalengo/redis @kalengo/util -u && npm i || exit $? 7 | cd ../../ 8 | done 9 | -------------------------------------------------------------------------------- /common/Makefile: -------------------------------------------------------------------------------- 1 | NAME=klg-nest-starter 2 | REGISTRY=registry.cn-shenzhen.aliyuncs.com 3 | TAG = beta 4 | 5 | build: 6 | echo building ${NAME}:${TAG} 7 | docker build -t ${REGISTRY}/${NAME}:${TAG} . 8 | docker push "${REGISTRY}/${NAME}:${TAG}" 9 | -------------------------------------------------------------------------------- /sample/hello-world/.dockerignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules/ 3 | 4 | # build 5 | dist/ 6 | 7 | logs/ 8 | # tests 9 | coverage/ 10 | .nyc_output/ 11 | 12 | # IDE 13 | /.idea 14 | /.awcache 15 | /.vscode 16 | *.code-workspace 17 | 18 | -------------------------------------------------------------------------------- /sample/nest-keycloak/.dockerignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules/ 3 | 4 | # build 5 | dist/ 6 | 7 | logs/ 8 | # tests 9 | coverage/ 10 | .nyc_output/ 11 | 12 | # IDE 13 | /.idea 14 | /.awcache 15 | /.vscode 16 | *.code-workspace 17 | 18 | -------------------------------------------------------------------------------- /sample/nest-simple/.dockerignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules/ 3 | 4 | # build 5 | dist/ 6 | 7 | logs/ 8 | # tests 9 | coverage/ 10 | .nyc_output/ 11 | 12 | # IDE 13 | /.idea 14 | /.awcache 15 | /.vscode 16 | *.code-workspace 17 | 18 | -------------------------------------------------------------------------------- /sample/nest-zeebe/.dockerignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules/ 3 | 4 | # build 5 | dist/ 6 | 7 | logs/ 8 | # tests 9 | coverage/ 10 | .nyc_output/ 11 | 12 | # IDE 13 | /.idea 14 | /.awcache 15 | /.vscode 16 | *.code-workspace 17 | 18 | -------------------------------------------------------------------------------- /sample/redis-redlock-cache/config/default.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: process.env.PORT || 3000, 3 | redis: { 4 | uri: process.env.REDIS_URI || 'redis://localhost:6379', 5 | prefix: process.env.REDIS_PREFIX || 'redis_' 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /packages/web/index.ts: -------------------------------------------------------------------------------- 1 | export * from './src/http.exception.filter' 2 | export * from './src/transform.interceptor' 3 | export * from './src/base.response' 4 | export * from './src/validation.pipe' 5 | export * from './src/exceptions/business.exception' 6 | -------------------------------------------------------------------------------- /sample/nest-ms-consumer/.dockerignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules/ 3 | 4 | # build 5 | dist/ 6 | 7 | logs/ 8 | # tests 9 | coverage/ 10 | .nyc_output/ 11 | 12 | # IDE 13 | /.idea 14 | /.awcache 15 | /.vscode 16 | *.code-workspace 17 | 18 | -------------------------------------------------------------------------------- /sample/nest-ms-provider/.dockerignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules/ 3 | 4 | # build 5 | dist/ 6 | 7 | logs/ 8 | # tests 9 | coverage/ 10 | .nyc_output/ 11 | 12 | # IDE 13 | /.idea 14 | /.awcache 15 | /.vscode 16 | *.code-workspace 17 | 18 | -------------------------------------------------------------------------------- /sample/hello-world/Makefile: -------------------------------------------------------------------------------- 1 | NAME=klg-nest-starter 2 | REGISTRY=registry.cn-shenzhen.aliyuncs.com 3 | TAG = beta 4 | 5 | build: 6 | echo building ${NAME}:${TAG} 7 | docker build -t ${REGISTRY}/${NAME}:${TAG} . 8 | docker push "${REGISTRY}/${NAME}:${TAG}" 9 | -------------------------------------------------------------------------------- /sample/nest-keycloak/Makefile: -------------------------------------------------------------------------------- 1 | NAME=klg-nest-starter 2 | REGISTRY=registry.cn-shenzhen.aliyuncs.com 3 | TAG = beta 4 | 5 | build: 6 | echo building ${NAME}:${TAG} 7 | docker build -t ${REGISTRY}/${NAME}:${TAG} . 8 | docker push "${REGISTRY}/${NAME}:${TAG}" 9 | -------------------------------------------------------------------------------- /sample/nest-simple/Makefile: -------------------------------------------------------------------------------- 1 | NAME=klg-nest-starter 2 | REGISTRY=registry.cn-shenzhen.aliyuncs.com 3 | TAG = beta 4 | 5 | build: 6 | echo building ${NAME}:${TAG} 7 | docker build -t ${REGISTRY}/${NAME}:${TAG} . 8 | docker push "${REGISTRY}/${NAME}:${TAG}" 9 | -------------------------------------------------------------------------------- /sample/nest-zeebe/Makefile: -------------------------------------------------------------------------------- 1 | NAME=klg-nest-starter 2 | REGISTRY=registry.cn-shenzhen.aliyuncs.com 3 | TAG = beta 4 | 5 | build: 6 | echo building ${NAME}:${TAG} 7 | docker build -t ${REGISTRY}/${NAME}:${TAG} . 8 | docker push "${REGISTRY}/${NAME}:${TAG}" 9 | -------------------------------------------------------------------------------- /sample/redis-redlock-cache/.dockerignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules/ 3 | 4 | # build 5 | dist/ 6 | 7 | logs/ 8 | # tests 9 | coverage/ 10 | .nyc_output/ 11 | 12 | # IDE 13 | /.idea 14 | /.awcache 15 | /.vscode 16 | *.code-workspace 17 | 18 | -------------------------------------------------------------------------------- /packages/mongoose/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /sample/nest-ms-consumer/Makefile: -------------------------------------------------------------------------------- 1 | NAME=klg-nest-starter 2 | REGISTRY=registry.cn-shenzhen.aliyuncs.com 3 | TAG = beta 4 | 5 | build: 6 | echo building ${NAME}:${TAG} 7 | docker build -t ${REGISTRY}/${NAME}:${TAG} . 8 | docker push "${REGISTRY}/${NAME}:${TAG}" 9 | -------------------------------------------------------------------------------- /sample/nest-ms-provider/Makefile: -------------------------------------------------------------------------------- 1 | NAME=klg-nest-starter 2 | REGISTRY=registry.cn-shenzhen.aliyuncs.com 3 | TAG = beta 4 | 5 | build: 6 | echo building ${NAME}:${TAG} 7 | docker build -t ${REGISTRY}/${NAME}:${TAG} . 8 | docker push "${REGISTRY}/${NAME}:${TAG}" 9 | -------------------------------------------------------------------------------- /sample/redis-redlock-cache/Makefile: -------------------------------------------------------------------------------- 1 | NAME=klg-nest-starter 2 | REGISTRY=registry.cn-shenzhen.aliyuncs.com 3 | TAG = beta 4 | 5 | build: 6 | echo building ${NAME}:${TAG} 7 | docker build -t ${REGISTRY}/${NAME}:${TAG} . 8 | docker push "${REGISTRY}/${NAME}:${TAG}" 9 | -------------------------------------------------------------------------------- /packages/keycloak/src/interface/keycloak-connect-options.interface.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Keycloak Connect options. 3 | */ 4 | export interface KeycloakConnectOptions { 5 | /** 6 | * Authentication redirect to login page 7 | */ 8 | login_redirect?: boolean 9 | } 10 | -------------------------------------------------------------------------------- /packages/redis/src/redlock/redlock.constants.ts: -------------------------------------------------------------------------------- 1 | export const REDLOCK_SERVICE = Symbol('REDLOCK_SERVICE') 2 | export const MUTEX_LOCK = Symbol('MUTEX_LOCK') 3 | export const BUFFER_LOCK = Symbol('BUFFER_LOCK') 4 | export const REDLOCK_MODULE_OPTIONS = Symbol('REDIS_MODULE_OPTIONS') 5 | -------------------------------------------------------------------------------- /packages/redis/src/redlock/redlock.interface.ts: -------------------------------------------------------------------------------- 1 | export interface LockOption { 2 | retryCount: number 3 | retryDelay?: number 4 | } 5 | 6 | export type BufferOptions = LockOption 7 | 8 | export interface DecoratorLockOption { 9 | key?: string 10 | ttl?: number 11 | } 12 | -------------------------------------------------------------------------------- /sample/nest-zeebe/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": [ 3 | "js", 4 | "json", 5 | "ts" 6 | ], 7 | "rootDir": ".", 8 | "testEnvironment": "node", 9 | "testRegex": ".e2e-spec.ts$", 10 | "transform": { 11 | "^.+\\.(t|j)s$": "ts-jest" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /docs/extend/crud.md: -------------------------------------------------------------------------------- 1 | Nest 官方文档有 [CRUD](https://docs.nestjs.cn/7/recipes?id=crud) 的相关教程, 2 | 但是目前只支持 TypeORM,据说未来会支持 mongoose。 3 | 4 | 这里推荐大家使用 [nestjs-mongoose-crud](https://github.com/topfullstack/nestjs-mongoose-crud) 这个开源项目, 5 | 支持 Typegoose,也支持 Swagger 文档,非常适合我们。 6 | 7 | 可以配合 @kalengo/mongoose 使用 8 | 9 | -------------------------------------------------------------------------------- /packages/Readme.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 本工具服务于 Kalengo Nest Starter。 4 | Kalengo Nest Starter 一个 Nest 脚手架,是 Kalengo 适配版本,针对 Kalengo 的实际需求对 Nest 做了适配. 5 | 6 | 详细内容见文档 7 | 8 | [Kalengo Nest Starter](https://kaolalicai.github.io/nest_doc/) 9 | 10 | [开发流程](https://github.com/kaolalicai/klg-nest-starter) 11 | -------------------------------------------------------------------------------- /packages/redis/Readme.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 本工具服务于 Kalengo Nest Starter。 4 | Kalengo Nest Starter 一个 Nest 脚手架,是 Kalengo 适配版本,针对 Kalengo 的实际需求对 Nest 做了适配. 5 | 6 | 详细内容见文档 7 | 8 | [Kalengo Nest Starter](https://kaolalicai.github.io/nest_doc/) 9 | 10 | [开发流程](https://github.com/kaolalicai/klg-nest-starter) 11 | -------------------------------------------------------------------------------- /packages/utils/Readme.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 本工具服务于 Kalengo Nest Starter。 4 | Kalengo Nest Starter 一个 Nest 脚手架,是 Kalengo 适配版本,针对 Kalengo 的实际需求对 Nest 做了适配. 5 | 6 | 详细内容见文档 7 | 8 | [Kalengo Nest Starter](https://kaolalicai.github.io/nest_doc/) 9 | 10 | [开发流程](https://github.com/kaolalicai/klg-nest-starter) 11 | -------------------------------------------------------------------------------- /packages/web/Readme.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 本工具服务于 Kalengo Nest Starter。 4 | Kalengo Nest Starter 一个 Nest 脚手架,是 Kalengo 适配版本,针对 Kalengo 的实际需求对 Nest 做了适配. 5 | 6 | 详细内容见文档 7 | 8 | [Kalengo Nest Starter](https://kaolalicai.github.io/nest_doc/) 9 | 10 | [开发流程](https://github.com/kaolalicai/klg-nest-starter) 11 | -------------------------------------------------------------------------------- /sample/nest-keycloak/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { UsersModule } from './users/users.module' 3 | import { KeycloakConnectModule } from '@kalengo/keycloak' 4 | 5 | @Module({ 6 | imports: [KeycloakConnectModule.forRoot({}), UsersModule] 7 | }) 8 | export class ApplicationModule {} 9 | -------------------------------------------------------------------------------- /packages/keycloak/Readme.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 本工具服务于 Kalengo Nest Starter。 4 | Kalengo Nest Starter 一个 Nest 脚手架,是 Kalengo 适配版本,针对 Kalengo 的实际需求对 Nest 做了适配. 5 | 6 | 详细内容见文档 7 | 8 | [Kalengo Nest Starter](https://kaolalicai.github.io/nest_doc/) 9 | 10 | [开发流程](https://github.com/kaolalicai/klg-nest-starter) 11 | -------------------------------------------------------------------------------- /packages/mongoose/Readme.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 本工具服务于 Kalengo Nest Starter。 4 | Kalengo Nest Starter 一个 Nest 脚手架,是 Kalengo 适配版本,针对 Kalengo 的实际需求对 Nest 做了适配. 5 | 6 | 详细内容见文档 7 | 8 | [Kalengo Nest Starter](https://kaolalicai.github.io/nest_doc/) 9 | 10 | [开发流程](https://github.com/kaolalicai/klg-nest-starter) 11 | -------------------------------------------------------------------------------- /packages/web/src/exceptions/business.exception.ts: -------------------------------------------------------------------------------- 1 | import { HttpException, HttpStatus } from '@nestjs/common' 2 | 3 | export class BusinessException extends HttpException { 4 | constructor() { 5 | super('Business Error', HttpStatus.INTERNAL_SERVER_ERROR) 6 | } 7 | 8 | getCode() { 9 | return 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /sample/hello-world/src/settings.ts: -------------------------------------------------------------------------------- 1 | import * as morgan from 'morgan' 2 | import { INestApplication } from '@nestjs/common' 3 | 4 | export const prefix = 'api/v1' 5 | 6 | export function appSettings(app: INestApplication) { 7 | app.setGlobalPrefix(prefix) 8 | 9 | // request log 10 | app.use(morgan('tiny')) 11 | } 12 | -------------------------------------------------------------------------------- /sample/nest-simple/src/users/users.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { UsersController } from './users.controller' 3 | import { UsersService } from './users.service' 4 | 5 | @Module({ 6 | controllers: [UsersController], 7 | providers: [UsersService] 8 | }) 9 | export class UsersModule {} 10 | -------------------------------------------------------------------------------- /template/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | name: { 5 | desc: 'project name', 6 | }, 7 | description: { 8 | desc: 'project description', 9 | default: 'kalengo backend server' 10 | }, 11 | author: { 12 | desc: 'project author', 13 | default: 'kalengo team' 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /sample/nest-ms-provider/src/users/users.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { UsersController } from './users.controller' 3 | import { UsersService } from './users.service' 4 | 5 | @Module({ 6 | controllers: [UsersController], 7 | providers: [UsersService] 8 | }) 9 | export class UsersModule {} 10 | -------------------------------------------------------------------------------- /sample/nest-keycloak/src/users/users.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { UsersController } from './users.controller' 3 | import { UsersService } from './users.service' 4 | 5 | @Module({ 6 | imports: [], 7 | controllers: [UsersController], 8 | providers: [UsersService] 9 | }) 10 | export class UsersModule {} 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules/ 3 | package-lock.json 4 | 5 | # tests 6 | coverage/ 7 | .nyc_output/ 8 | 9 | # IDE 10 | /.idea 11 | /.awcache 12 | /.vscode 13 | *.code-workspace 14 | 15 | # bundle 16 | packages/**/*.d.ts 17 | packages/**/*.js 18 | *.log 19 | morph.ts 20 | 21 | # documentation 22 | dist/ 23 | boilerplate 24 | temp 25 | -------------------------------------------------------------------------------- /sample/hello-world/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "setupFilesAfterEnv": ["/test-helper.ts"], 3 | "moduleFileExtensions": [ 4 | "js", 5 | "json", 6 | "ts" 7 | ], 8 | "rootDir": ".", 9 | "testEnvironment": "node", 10 | "testRegex": ".e2e-spec.ts$", 11 | "transform": { 12 | "^.+\\.(t|j)s$": "ts-jest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /sample/nest-keycloak/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "setupFilesAfterEnv": ["/test-helper.ts"], 3 | "moduleFileExtensions": [ 4 | "js", 5 | "json", 6 | "ts" 7 | ], 8 | "rootDir": ".", 9 | "testEnvironment": "node", 10 | "testRegex": ".e2e-spec.ts$", 11 | "transform": { 12 | "^.+\\.(t|j)s$": "ts-jest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /sample/nest-simple/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "setupFilesAfterEnv": ["/test-helper.ts"], 3 | "moduleFileExtensions": [ 4 | "js", 5 | "json", 6 | "ts" 7 | ], 8 | "rootDir": ".", 9 | "testEnvironment": "node", 10 | "testRegex": ".e2e-spec.ts$", 11 | "transform": { 12 | "^.+\\.(t|j)s$": "ts-jest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /sample/redis-redlock-cache/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { UsersModule } from './users/users.module' 3 | import { RedisModuleBuilder, RedlockModule } from '@kalengo/redis' 4 | 5 | @Module({ 6 | imports: [RedisModuleBuilder.forRoot(), RedlockModule.forRoot(), UsersModule] 7 | }) 8 | export class ApplicationModule {} 9 | -------------------------------------------------------------------------------- /sample/redis-redlock-cache/src/users/users.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { UsersController } from './users.controller' 3 | import { UsersService } from './users.service' 4 | 5 | @Module({ 6 | imports: [], 7 | controllers: [UsersController], 8 | providers: [UsersService] 9 | }) 10 | export class UsersModule {} 11 | -------------------------------------------------------------------------------- /packages/keycloak/index.ts: -------------------------------------------------------------------------------- 1 | export * from './src/interface/keycloak-connect-options.interface' 2 | export * from './src/constants' 3 | export * from './src/decorators/roles.decorator' 4 | export * from './src/keycloak.module' 5 | export * from './src/guards/auth.guard' 6 | export * from './src/guards/roles.guard' 7 | export { Keycloak } from 'keycloak-connect' 8 | -------------------------------------------------------------------------------- /sample/nest-ms-consumer/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "setupFilesAfterEnv": ["/test-helper.ts"], 3 | "moduleFileExtensions": [ 4 | "js", 5 | "json", 6 | "ts" 7 | ], 8 | "rootDir": ".", 9 | "testEnvironment": "node", 10 | "testRegex": ".e2e-spec.ts$", 11 | "transform": { 12 | "^.+\\.(t|j)s$": "ts-jest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /sample/nest-ms-provider/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "setupFilesAfterEnv": ["/test-helper.ts"], 3 | "moduleFileExtensions": [ 4 | "js", 5 | "json", 6 | "ts" 7 | ], 8 | "rootDir": ".", 9 | "testEnvironment": "node", 10 | "testRegex": ".e2e-spec.ts$", 11 | "transform": { 12 | "^.+\\.(t|j)s$": "ts-jest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /sample/redis-redlock-cache/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "setupFilesAfterEnv": ["/test-helper.ts"], 3 | "moduleFileExtensions": [ 4 | "js", 5 | "json", 6 | "ts" 7 | ], 8 | "rootDir": ".", 9 | "testEnvironment": "node", 10 | "testRegex": ".e2e-spec.ts$", 11 | "transform": { 12 | "^.+\\.(t|j)s$": "ts-jest" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /sample/nest-keycloak/src/users/users.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty } from 'class-validator' 2 | import { BaseResponse } from '@kalengo/web' 3 | 4 | export class UserDto { 5 | @IsNotEmpty() 6 | readonly name!: string 7 | @IsNotEmpty() 8 | readonly phone!: string 9 | } 10 | 11 | export class RegisterRes extends BaseResponse { 12 | readonly data!: UserDto 13 | } 14 | -------------------------------------------------------------------------------- /sample/nest-simple/src/users/users.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | import { UserDto } from './users.dto' 3 | 4 | @Injectable() 5 | export class UsersService { 6 | async register(createUsersDto: UserDto): Promise { 7 | return createUsersDto 8 | } 9 | 10 | async findAll(): Promise { 11 | return [1, 2, 3, 4] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /sample/nest-keycloak/src/users/users.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | import { UserDto } from './users.dto' 3 | 4 | @Injectable() 5 | export class UsersService { 6 | async register(createUsersDto: UserDto): Promise { 7 | return createUsersDto 8 | } 9 | 10 | async findAll(): Promise { 11 | return [1, 2, 3, 4, 5] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /sample/nest-ms-consumer/src/users/users.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | import { UserDto } from './users.dto' 3 | 4 | @Injectable() 5 | export class UsersService { 6 | async register(createUsersDto: UserDto): Promise { 7 | return createUsersDto 8 | } 9 | 10 | async findAll(): Promise { 11 | return [1, 2, 3, 4] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /sample/nest-ms-provider/src/users/users.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | import { UserDto } from './users.dto' 3 | 4 | @Injectable() 5 | export class UsersService { 6 | async register(createUsersDto: UserDto): Promise { 7 | return createUsersDto 8 | } 9 | 10 | async findAll(): Promise { 11 | return [1, 2, 3, 4] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/redis/index.ts: -------------------------------------------------------------------------------- 1 | export * from './src/ioredis/redis.module.builder' 2 | export * from './src/redlock/redlock.module' 3 | export * from './src/redlock/Redlock' 4 | export * from './src/redlock/redlock.service' 5 | export * from './src/redlock/redlock.interface' 6 | export * from './src/redlock/redlock.constants' 7 | export * from './src/decorators/lock.decorator' 8 | export * from 'nestjs-redis' 9 | -------------------------------------------------------------------------------- /sample/nest-zeebe/config/dev.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: process.env.PORT || 3000, 3 | schedule: true, 4 | mongodb: { 5 | debug: true, 6 | connections: [ 7 | { 8 | url: 'mongodb://localhost:57017/zeebe', 9 | options: { 10 | useNewUrlParser: true, 11 | useUnifiedTopology: true 12 | } 13 | } 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /sample/hello-world/test/users/users-hello.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { request, prefix } from '../test-helper' 2 | 3 | describe('AppController (e2e)', () => { 4 | it('hello', () => { 5 | return request 6 | .get(prefix + '/users/hello') 7 | .expect(200) 8 | .expect({ 9 | code: 0, 10 | data: 'Hello World!', 11 | message: 'success' 12 | }) 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /sample/hello-world/src/users/model/account.model.ts: -------------------------------------------------------------------------------- 1 | import { prop, DocumentType, ReturnModelType } from '@typegoose/typegoose' 2 | 3 | export class Account { 4 | @prop({ required: true, index: true }) 5 | userId!: string 6 | 7 | @prop({ default: 0 }) 8 | balance!: number 9 | } 10 | 11 | export type IAccountModel = DocumentType 12 | export type AccountModel = ReturnModelType 13 | -------------------------------------------------------------------------------- /packages/keycloak/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@kalengo/keycloak", 3 | "version": "1.0.0", 4 | "description": "nest redis for kalengo", 5 | "scripts": { 6 | "test": "npm run test" 7 | }, 8 | "publishConfig": { 9 | "access": "public" 10 | }, 11 | "author": "nick", 12 | "license": "MIT", 13 | "dependencies": { 14 | "@nestjs/common": "7.0.8", 15 | "keycloak-connect": "^9.0.3" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | "module.exports = {extends: ['@commitlint/config-conventional']}" 2 | 3 | module.exports = { 4 | extends: ['@commitlint/config-conventional'], 5 | rules: { 6 | 'type-enum': [2, 'always', [ 7 | "feat", "fix", "docs", "style", "refactor", "perf", "test", "build", "ci", "chore", "revert" 8 | ]], 9 | 'subject-full-stop': [0, 'never'], 10 | 'subject-case': [0, 'never'] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /common/commitlint.config.js: -------------------------------------------------------------------------------- 1 | "module.exports = {extends: ['@commitlint/config-conventional']}" 2 | 3 | module.exports = { 4 | extends: ['@commitlint/config-conventional'], 5 | rules: { 6 | 'type-enum': [2, 'always', [ 7 | "feat", "fix", "docs", "style", "refactor", "perf", "test", "build", "ci", "chore", "revert" 8 | ]], 9 | 'subject-full-stop': [0, 'never'], 10 | 'subject-case': [0, 'never'] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /sample/hello-world/config/test.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: process.env.PORT || 3000, 3 | mongodb: { 4 | debug: true, 5 | connections: [ 6 | { 7 | name: 'core', 8 | url: 'mongodb://localhost:57017/core_test', 9 | options: {} 10 | }, 11 | { 12 | name: 'app', 13 | url: 'mongodb://localhost:57017/app_test', 14 | options: {} 15 | } 16 | ] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sample/hello-world/test/users/users-find.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { request, genFixtures, prefix } from '../test-helper' 2 | import { UserTemplate } from '../model-mock-template' 3 | 4 | describe('AppController (e2e) find with fixtures ', () => { 5 | beforeAll(async function () { 6 | await genFixtures(UserTemplate, 1, 'User') 7 | }) 8 | 9 | it('find all', () => { 10 | request.get(prefix + '/users').expect(200) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /sample/nest-simple/config/test.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: process.env.PORT || 3000, 3 | mongodb: { 4 | debug: true, 5 | connections: [ 6 | { 7 | name: 'core', 8 | url: 'mongodb://localhost:57017/core_test', 9 | options: {} 10 | }, 11 | { 12 | name: 'app', 13 | url: 'mongodb://localhost:57017/app_test', 14 | options: {} 15 | } 16 | ] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sample/nest-zeebe/config/test.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: process.env.PORT || 3000, 3 | mongodb: { 4 | debug: true, 5 | connections: [ 6 | { 7 | name: 'core', 8 | url: 'mongodb://localhost:57017/core_test', 9 | options: {} 10 | }, 11 | { 12 | name: 'app', 13 | url: 'mongodb://localhost:57017/app_test', 14 | options: {} 15 | } 16 | ] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sample/hello-world/commitlint.config.js: -------------------------------------------------------------------------------- 1 | "module.exports = {extends: ['@commitlint/config-conventional']}" 2 | 3 | module.exports = { 4 | extends: ['@commitlint/config-conventional'], 5 | rules: { 6 | 'type-enum': [2, 'always', [ 7 | "feat", "fix", "docs", "style", "refactor", "perf", "test", "build", "ci", "chore", "revert" 8 | ]], 9 | 'subject-full-stop': [0, 'never'], 10 | 'subject-case': [0, 'never'] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /sample/nest-ms-consumer/config/test.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: process.env.PORT || 3000, 3 | mongodb: { 4 | debug: true, 5 | connections: [ 6 | { 7 | name: 'core', 8 | url: 'mongodb://localhost:57017/core_test', 9 | options: {} 10 | }, 11 | { 12 | name: 'app', 13 | url: 'mongodb://localhost:57017/app_test', 14 | options: {} 15 | } 16 | ] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sample/nest-ms-provider/config/test.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: process.env.PORT || 3000, 3 | mongodb: { 4 | debug: true, 5 | connections: [ 6 | { 7 | name: 'core', 8 | url: 'mongodb://localhost:57017/core_test', 9 | options: {} 10 | }, 11 | { 12 | name: 'app', 13 | url: 'mongodb://localhost:57017/app_test', 14 | options: {} 15 | } 16 | ] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sample/nest-simple/commitlint.config.js: -------------------------------------------------------------------------------- 1 | "module.exports = {extends: ['@commitlint/config-conventional']}" 2 | 3 | module.exports = { 4 | extends: ['@commitlint/config-conventional'], 5 | rules: { 6 | 'type-enum': [2, 'always', [ 7 | "feat", "fix", "docs", "style", "refactor", "perf", "test", "build", "ci", "chore", "revert" 8 | ]], 9 | 'subject-full-stop': [0, 'never'], 10 | 'subject-case': [0, 'never'] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /sample/nest-zeebe/commitlint.config.js: -------------------------------------------------------------------------------- 1 | "module.exports = {extends: ['@commitlint/config-conventional']}" 2 | 3 | module.exports = { 4 | extends: ['@commitlint/config-conventional'], 5 | rules: { 6 | 'type-enum': [2, 'always', [ 7 | "feat", "fix", "docs", "style", "refactor", "perf", "test", "build", "ci", "chore", "revert" 8 | ]], 9 | 'subject-full-stop': [0, 'never'], 10 | 'subject-case': [0, 'never'] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /sample/nest-keycloak/commitlint.config.js: -------------------------------------------------------------------------------- 1 | "module.exports = {extends: ['@commitlint/config-conventional']}" 2 | 3 | module.exports = { 4 | extends: ['@commitlint/config-conventional'], 5 | rules: { 6 | 'type-enum': [2, 'always', [ 7 | "feat", "fix", "docs", "style", "refactor", "perf", "test", "build", "ci", "chore", "revert" 8 | ]], 9 | 'subject-full-stop': [0, 'never'], 10 | 'subject-case': [0, 'never'] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /sample/nest-ms-consumer/commitlint.config.js: -------------------------------------------------------------------------------- 1 | "module.exports = {extends: ['@commitlint/config-conventional']}" 2 | 3 | module.exports = { 4 | extends: ['@commitlint/config-conventional'], 5 | rules: { 6 | 'type-enum': [2, 'always', [ 7 | "feat", "fix", "docs", "style", "refactor", "perf", "test", "build", "ci", "chore", "revert" 8 | ]], 9 | 'subject-full-stop': [0, 'never'], 10 | 'subject-case': [0, 'never'] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /sample/nest-ms-provider/commitlint.config.js: -------------------------------------------------------------------------------- 1 | "module.exports = {extends: ['@commitlint/config-conventional']}" 2 | 3 | module.exports = { 4 | extends: ['@commitlint/config-conventional'], 5 | rules: { 6 | 'type-enum': [2, 'always', [ 7 | "feat", "fix", "docs", "style", "refactor", "perf", "test", "build", "ci", "chore", "revert" 8 | ]], 9 | 'subject-full-stop': [0, 'never'], 10 | 'subject-case': [0, 'never'] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tools/gulp/util/task-helpers.ts: -------------------------------------------------------------------------------- 1 | import {readdirSync, statSync} from 'fs' 2 | import {join} from 'path' 3 | 4 | function isDirectory (path: string) { 5 | return statSync(path).isDirectory() 6 | } 7 | 8 | export function getFolders (dir: string) { 9 | return readdirSync(dir).filter(file => isDirectory(join(dir, file))) 10 | } 11 | 12 | export function getDirs (base: string) { 13 | return getFolders(base).map(path => `${base}/${path}`) 14 | } 15 | -------------------------------------------------------------------------------- /sample/redis-redlock-cache/commitlint.config.js: -------------------------------------------------------------------------------- 1 | "module.exports = {extends: ['@commitlint/config-conventional']}" 2 | 3 | module.exports = { 4 | extends: ['@commitlint/config-conventional'], 5 | rules: { 6 | 'type-enum': [2, 'always', [ 7 | "feat", "fix", "docs", "style", "refactor", "perf", "test", "build", "ci", "chore", "revert" 8 | ]], 9 | 'subject-full-stop': [0, 'never'], 10 | 'subject-case': [0, 'never'] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /common/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "skipLibCheck": true, 9 | "strict": true, 10 | "target": "es2017", 11 | "sourceMap": true, 12 | "outDir": "./dist", 13 | "baseUrl": "./", 14 | "incremental": true 15 | }, 16 | "exclude": ["node_modules", "dist"] 17 | } 18 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * Load the TypeScript compiler, then load the TypeScript gulpfile which simply loads all 4 | * the tasks. The tasks are really inside tools/gulp/tasks. 5 | */ 6 | 7 | const path = require('path'); 8 | 9 | const projectDir = __dirname; 10 | const tsconfigPath = path.join(projectDir, 'tools/gulp/tsconfig.json'); 11 | 12 | require('ts-node').register({ 13 | project: tsconfigPath 14 | }); 15 | 16 | require('./tools/gulp/gulpfile'); -------------------------------------------------------------------------------- /packages/mongoose/index.ts: -------------------------------------------------------------------------------- 1 | export * from './src/objectId' 2 | export * from './src/typegoose.module.builder' 3 | export * from './src/test.database.helper' 4 | export * from './src/typegoose' 5 | export * from '@typegoose/typegoose' 6 | // export * from './src/typegoose/typegoose.decorators'; 7 | // export * from './src/typegoose/typegoose.module'; 8 | // export * from './src/typegoose/typegoose.utils'; 9 | // export * from './src/typegoose/typegoose-options.interface'; 10 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | heroText: Kalengo Nest Starter 4 | tagline: Kalengo Node 后端 Nest 框架脚手架 5 | actionText: 快速上手 → 6 | actionLink: /quickstart/ 7 | features: 8 | - title: Kalengo 定制 9 | details: 源于 Kalengo 的内部开发经验,提供更简单的 Config,支持 Typegoose,提供 Log Redis RabbitMQ 等定制 10 | - title: Nest + TypeScript 驱动 11 | details: 使用 TypeScript 编写,基于 Nest 框架,拥抱开源社区 12 | - title: 更严格的限制 13 | details: 默认开启 TypeScript 严格模式,编写更容易维护的代码。 14 | footer: MIT Licensed 15 | --- 16 | -------------------------------------------------------------------------------- /sample/hello-world/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "skipLibCheck": true, 9 | "strict": true, 10 | "target": "es2017", 11 | "sourceMap": true, 12 | "outDir": "./dist", 13 | "baseUrl": "./", 14 | "incremental": true 15 | }, 16 | "exclude": ["node_modules", "dist"] 17 | } 18 | -------------------------------------------------------------------------------- /sample/nest-keycloak/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "skipLibCheck": true, 9 | "strict": true, 10 | "target": "es2017", 11 | "sourceMap": true, 12 | "outDir": "./dist", 13 | "baseUrl": "./", 14 | "incremental": true 15 | }, 16 | "exclude": ["node_modules", "dist"] 17 | } 18 | -------------------------------------------------------------------------------- /sample/nest-simple/src/users/users.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty } from 'class-validator' 2 | import { BaseResponse } from '@kalengo/web' 3 | 4 | export class UserDto { 5 | @IsNotEmpty() 6 | readonly name!: string 7 | @IsNotEmpty() 8 | readonly phone!: string 9 | } 10 | 11 | export class AccountDto { 12 | @IsNotEmpty() 13 | userId!: string 14 | 15 | balance!: number 16 | } 17 | 18 | export class RegisterRes extends BaseResponse { 19 | readonly data!: UserDto 20 | } 21 | -------------------------------------------------------------------------------- /sample/nest-simple/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "skipLibCheck": true, 9 | "strict": true, 10 | "target": "es2017", 11 | "sourceMap": true, 12 | "outDir": "./dist", 13 | "baseUrl": "./", 14 | "incremental": true 15 | }, 16 | "exclude": ["node_modules", "dist"] 17 | } 18 | -------------------------------------------------------------------------------- /sample/nest-zeebe/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "skipLibCheck": true, 9 | "strict": true, 10 | "target": "es2017", 11 | "sourceMap": true, 12 | "outDir": "./dist", 13 | "baseUrl": "./", 14 | "incremental": true 15 | }, 16 | "exclude": ["node_modules", "dist"] 17 | } 18 | -------------------------------------------------------------------------------- /sample/nest-keycloak/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core' 2 | import { ApplicationModule } from './app.module' 3 | import { appSettings } from './settings' 4 | 5 | async function bootstrap() { 6 | const app = await NestFactory.create(ApplicationModule) 7 | appSettings(app) 8 | 9 | await app.listen(process.env.PORT || 3000) 10 | console.log( 11 | `Application(${process.env.NODE_ENV}) is running on: ${await app.getUrl()}` 12 | ) 13 | } 14 | 15 | bootstrap() 16 | -------------------------------------------------------------------------------- /sample/nest-ms-consumer/src/users/users.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty } from 'class-validator' 2 | import { BaseResponse } from '@kalengo/web' 3 | 4 | export class UserDto { 5 | @IsNotEmpty() 6 | readonly name!: string 7 | @IsNotEmpty() 8 | readonly phone!: string 9 | } 10 | 11 | export class AccountDto { 12 | @IsNotEmpty() 13 | userId!: string 14 | 15 | balance!: number 16 | } 17 | 18 | export class RegisterRes extends BaseResponse { 19 | readonly data!: UserDto 20 | } 21 | -------------------------------------------------------------------------------- /sample/nest-ms-consumer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "skipLibCheck": true, 9 | "strict": true, 10 | "target": "es2017", 11 | "sourceMap": true, 12 | "outDir": "./dist", 13 | "baseUrl": "./", 14 | "incremental": true 15 | }, 16 | "exclude": ["node_modules", "dist"] 17 | } 18 | -------------------------------------------------------------------------------- /sample/nest-ms-provider/src/users/users.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty } from 'class-validator' 2 | import { BaseResponse } from '@kalengo/web' 3 | 4 | export class UserDto { 5 | @IsNotEmpty() 6 | readonly name!: string 7 | @IsNotEmpty() 8 | readonly phone!: string 9 | } 10 | 11 | export class AccountDto { 12 | @IsNotEmpty() 13 | userId!: string 14 | 15 | balance!: number 16 | } 17 | 18 | export class RegisterRes extends BaseResponse { 19 | readonly data!: UserDto 20 | } 21 | -------------------------------------------------------------------------------- /sample/nest-ms-provider/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "skipLibCheck": true, 9 | "strict": true, 10 | "target": "es2017", 11 | "sourceMap": true, 12 | "outDir": "./dist", 13 | "baseUrl": "./", 14 | "incremental": true 15 | }, 16 | "exclude": ["node_modules", "dist"] 17 | } 18 | -------------------------------------------------------------------------------- /sample/nest-simple/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core' 2 | import { ApplicationModule } from './app.module' 3 | import { appSettings } from './settings' 4 | 5 | async function bootstrap() { 6 | const app = await NestFactory.create(ApplicationModule) 7 | appSettings(app) 8 | 9 | await app.listen(process.env.PORT || 3000) 10 | console.log( 11 | `Application(${process.env.NODE_ENV}) is running on: ${await app.getUrl()}` 12 | ) 13 | } 14 | 15 | bootstrap() 16 | -------------------------------------------------------------------------------- /sample/redis-redlock-cache/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "skipLibCheck": true, 9 | "strict": true, 10 | "target": "es2017", 11 | "sourceMap": true, 12 | "outDir": "./dist", 13 | "baseUrl": "./", 14 | "incremental": true 15 | }, 16 | "exclude": ["node_modules", "dist"] 17 | } 18 | -------------------------------------------------------------------------------- /sample/nest-ms-consumer/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core' 2 | import { ApplicationModule } from './app.module' 3 | import { appSettings } from './settings' 4 | 5 | async function bootstrap() { 6 | const app = await NestFactory.create(ApplicationModule) 7 | appSettings(app) 8 | 9 | await app.listen(process.env.PORT || 3000) 10 | console.log( 11 | `Application(${process.env.NODE_ENV}) is running on: ${await app.getUrl()}` 12 | ) 13 | } 14 | 15 | bootstrap() 16 | -------------------------------------------------------------------------------- /sample/hello-world/test/model-mock-template.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * mock js 的语法 见 https://github.com/nuysoft/Mock/wiki/Syntax-Specification 3 | */ 4 | import * as Mock from 'mockjs' 5 | import { ObjectId } from '@kalengo/mongoose' 6 | 7 | export const UserTemplate = { 8 | _id: () => ObjectId(), 9 | name: () => Mock.Random.first(), 10 | phone: /1\d{10}/ 11 | } 12 | 13 | export const AccountTemplate = { 14 | _id: () => ObjectId(), 15 | userId: () => ObjectId(), 16 | 'balance|10-100': 1 17 | } 18 | -------------------------------------------------------------------------------- /sample/nest-keycloak/config/test.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: process.env.PORT || 3000, 3 | keycloak: { 4 | realm: 'nodejs-example', 5 | 'auth-server-url': 'http://keycloak.sso.dev.smart2.cn/auth/', 6 | 'ssl-required': 'external', 7 | resource: 'nodejs-apiserver', 8 | 'verify-token-audience': false, 9 | credentials: { 10 | secret: 'c800c1a6-5c1a-4012-a008-6cf8bf6c50ad' 11 | }, 12 | 'confidential-port': 0, 13 | 'policy-enforcer': {} 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /sample/nest-keycloak/src/settings.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common' 2 | import { HttpExceptionFilter } from './common/filters/http-exception.filter' 3 | import { TransformInterceptor } from '@kalengo/web' 4 | 5 | export const prefix = 'api/v1' 6 | 7 | export function appSettings(app: INestApplication) { 8 | // app.setGlobalPrefix(prefix) 9 | 10 | // 错误处理和返回值format 11 | app.useGlobalFilters(new HttpExceptionFilter()) 12 | app.useGlobalInterceptors(new TransformInterceptor()) 13 | } 14 | -------------------------------------------------------------------------------- /packages/mongoose/src/typegoose/typegoose.decorators.ts: -------------------------------------------------------------------------------- 1 | import { Inject } from '@nestjs/common'; 2 | import { TypegooseClass } from './typegoose-class.interface'; 3 | import { getModelToken } from './typegoose.utils'; 4 | 5 | /** 6 | * Used to return the inject the mongoose model. 7 | * @param model - the model class wanted to be injected 8 | * @returns the annotation for injecting model 9 | * @internal 10 | */ 11 | export const InjectModel = (model: TypegooseClass) => 12 | Inject(getModelToken(model.name)); 13 | -------------------------------------------------------------------------------- /packages/redis/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@kalengo/redis", 3 | "version": "1.0.4", 4 | "description": "nest redis for kalengo", 5 | "scripts": { 6 | "test": "npm run test" 7 | }, 8 | "publishConfig": { 9 | "access": "public" 10 | }, 11 | "author": "nick", 12 | "license": "MIT", 13 | "dependencies": { 14 | "@nestjs/common": "7.0.8", 15 | "nestjs-redis": "^1.2.7", 16 | "redlock": "^4.1.0" 17 | }, 18 | "devDependencies": { 19 | "@types/redlock": "^4.0.1" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /sample/nest-keycloak/config/dev.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: process.env.PORT || 3000, 3 | schedule: true, 4 | keycloak: { 5 | realm: 'nodejs-example', 6 | 'auth-server-url': 'http://keycloak.sso.dev.smart2.cn/auth/', 7 | 'ssl-required': 'external', 8 | resource: 'nodejs-apiserver', 9 | 'verify-token-audience': false, 10 | credentials: { 11 | secret: 'c800c1a6-5c1a-4012-a008-6cf8bf6c50ad' 12 | }, 13 | 'confidential-port': 0, 14 | 'policy-enforcer': {} 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/redis/src/config.parse.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash' 2 | import * as config from 'config' 3 | 4 | export type RedisConfig = { 5 | uri: string 6 | prefix: string 7 | } 8 | 9 | export function parseConfig(): { redisConfig: RedisConfig } { 10 | let redisConfig: RedisConfig 11 | try { 12 | redisConfig = config.get('redis') 13 | } catch (e) { 14 | throw new Error('redis config 不能为空') 15 | } 16 | if (_.isEmpty(redisConfig)) throw new Error('mongodb config 不能为空') 17 | return { redisConfig } 18 | } 19 | -------------------------------------------------------------------------------- /sample/hello-world/src/users/model/user.model.ts: -------------------------------------------------------------------------------- 1 | import { prop, DocumentType, ReturnModelType } from '@typegoose/typegoose' 2 | 3 | export class User { 4 | @prop() 5 | name!: string 6 | 7 | @prop({ required: true, index: true, unique: true }) 8 | phone!: string 9 | 10 | @prop({ default: false }) 11 | isRegister!: boolean 12 | 13 | async registerSuccess() { 14 | this.isRegister = true 15 | } 16 | } 17 | 18 | export type IUserModel = DocumentType 19 | export type UserModel = ReturnModelType 20 | -------------------------------------------------------------------------------- /sample/redis-redlock-cache/test/main.ts: -------------------------------------------------------------------------------- 1 | import * as supertest from 'supertest' 2 | import { ApplicationModule } from '../src/app.module' 3 | import { Test } from '@nestjs/testing' 4 | 5 | export async function bootstrap() { 6 | // init nestjs 7 | const testModule = await Test.createTestingModule({ 8 | imports: [ApplicationModule] 9 | }).compile() 10 | const app = testModule.createNestApplication() 11 | await app.init() 12 | const request = supertest(app.getHttpServer()) 13 | return { app, request, testModule } 14 | } 15 | -------------------------------------------------------------------------------- /packages/utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@kalengo/utils", 3 | "version": "1.0.3", 4 | "description": "aka utils", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "npm run test" 8 | }, 9 | "publishConfig": { 10 | "access": "public" 11 | }, 12 | "author": "nick", 13 | "license": "MIT", 14 | "dependencies": { 15 | "config": "^3.3.1", 16 | "klg-date": "^2.0.1", 17 | "klg-logger": "^3.0.1", 18 | "lodash": "^4.17.15" 19 | }, 20 | "gitHead": "4b2499c98f7b2334e390a18ab220547f7e00ee29" 21 | } 22 | -------------------------------------------------------------------------------- /sample/hello-world/test/users/users-err-handle.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { request, prefix } from '../test-helper' 2 | 3 | describe('AppController (e2e) error handle ', () => { 4 | it('get err', async () => { 5 | const res = await request.get(prefix + '/users/err').expect(500) 6 | console.log('res status', res.status) 7 | console.log('res body', res.body) 8 | expect(res.status).toEqual(500) 9 | expect(res.body).toEqual({ 10 | code: 1, 11 | message: 'Business Error', 12 | url: '/api/v1/users/err' 13 | }) 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /packages/redis/src/redlock/redlock.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject } from '@nestjs/common' 2 | import { Redlock } from './Redlock' 3 | import { BUFFER_LOCK, MUTEX_LOCK } from './redlock.constants' 4 | 5 | @Injectable() 6 | export class RedlockService { 7 | constructor( 8 | @Inject(MUTEX_LOCK) private readonly mutex: Redlock, 9 | @Inject(BUFFER_LOCK) private readonly buffer: Redlock 10 | ) {} 11 | 12 | getMutex(): Redlock { 13 | return this.mutex 14 | } 15 | 16 | getBuffer(): Redlock { 17 | return this.buffer 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "noImplicitAny": false, 6 | "skipLibCheck": true, 7 | "noUnusedLocals": false, 8 | "removeComments": false, 9 | "noLib": false, 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "target": "es2020", 13 | "sourceMap": false, 14 | "allowJs": false, 15 | "strict": true, 16 | "strictNullChecks": false 17 | }, 18 | "exclude": [ 19 | "../node_modules" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /packages/utils/src/LoggerFactory.ts: -------------------------------------------------------------------------------- 1 | // logger 工厂,给用户自定义 2 | import { colorConsole, dailyfile } from 'tracer' 3 | import { defaultLogConfig } from './LoggerConfig' 4 | 5 | export function LogFactory(config?) { 6 | Object.assign(defaultLogConfig, config) 7 | let logger = colorConsole(defaultLogConfig) 8 | // 指定了存储地址的的要按日分割 9 | if (defaultLogConfig && defaultLogConfig.root) { 10 | defaultLogConfig.transport = function (data) { 11 | console.log(data.output) 12 | } 13 | logger = dailyfile(defaultLogConfig) 14 | } 15 | return logger 16 | } 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": false, 5 | "noImplicitAny": false, 6 | "noUnusedLocals": false, 7 | "removeComments": true, 8 | "noLib": false, 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es6", 12 | "sourceMap": false, 13 | "allowJs": true, 14 | "outDir": "dist", 15 | "lib": [ 16 | "es7" 17 | ] 18 | }, 19 | "include": [ 20 | "packages/**/*" 21 | ], 22 | "exclude": ["node_modules", "**/*.spec.ts"] 23 | } 24 | -------------------------------------------------------------------------------- /sample/hello-world/test/.run/E2E.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /sample/nest-simple/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { UsersModule } from './users/users.module' 3 | import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core' 4 | import { HttpExceptionFilter, TransformInterceptor } from '@kalengo/web' 5 | 6 | @Module({ 7 | imports: [UsersModule], 8 | providers: [ 9 | { 10 | provide: APP_FILTER, 11 | useClass: HttpExceptionFilter 12 | }, 13 | { 14 | provide: APP_INTERCEPTOR, 15 | useClass: TransformInterceptor 16 | } 17 | ] 18 | }) 19 | export class ApplicationModule {} 20 | -------------------------------------------------------------------------------- /packages/mongoose/src/typegoose/typegoose-class.interface.ts: -------------------------------------------------------------------------------- 1 | import { SchemaOptions } from 'mongoose'; 2 | 3 | export interface TypegooseClass { 4 | new (...args: any[]); 5 | } 6 | 7 | export interface TypegooseClassWrapper { 8 | typegooseClass: TypegooseClass; 9 | } 10 | 11 | export interface TypegooseClassWithOptions extends TypegooseClassWrapper { 12 | schemaOptions?: SchemaOptions; 13 | discriminators?: (TypegooseClass | TypegooseDiscriminator)[]; 14 | } 15 | 16 | export interface TypegooseDiscriminator extends TypegooseClassWrapper { 17 | discriminatorId?: string; 18 | } 19 | -------------------------------------------------------------------------------- /sample/nest-ms-consumer/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { UsersModule } from './users/users.module' 3 | import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core' 4 | import { HttpExceptionFilter, TransformInterceptor } from '@kalengo/web' 5 | 6 | @Module({ 7 | imports: [UsersModule], 8 | providers: [ 9 | { 10 | provide: APP_FILTER, 11 | useClass: HttpExceptionFilter 12 | }, 13 | { 14 | provide: APP_INTERCEPTOR, 15 | useClass: TransformInterceptor 16 | } 17 | ] 18 | }) 19 | export class ApplicationModule {} 20 | -------------------------------------------------------------------------------- /sample/nest-ms-provider/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { UsersModule } from './users/users.module' 3 | import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core' 4 | import { HttpExceptionFilter, TransformInterceptor } from '@kalengo/web' 5 | 6 | @Module({ 7 | imports: [UsersModule], 8 | providers: [ 9 | { 10 | provide: APP_FILTER, 11 | useClass: HttpExceptionFilter 12 | }, 13 | { 14 | provide: APP_INTERCEPTOR, 15 | useClass: TransformInterceptor 16 | } 17 | ] 18 | }) 19 | export class ApplicationModule {} 20 | -------------------------------------------------------------------------------- /sample/nest-simple/src/users/users.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Post, Body } from '@nestjs/common' 2 | import { UsersService } from './users.service' 3 | import { UserDto } from './users.dto' 4 | 5 | @Controller('users') 6 | export class UsersController { 7 | constructor(private readonly usersService: UsersService) {} 8 | 9 | @Post('/register') 10 | async register(@Body() createUserDto: UserDto) { 11 | return await this.usersService.register(createUserDto) 12 | } 13 | 14 | @Get() 15 | async findAll() { 16 | return this.usersService.findAll() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/mongoose/src/typegoose/typegoose.constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The name for the default database connection provider. 3 | * @internal 4 | */ 5 | export const DEFAULT_DB_CONNECTION_NAME = 'DefaultTypegooseConnection'; 6 | 7 | /** 8 | * The provider name for the provider that gives the name of the database name provider. 9 | * @internal 10 | */ 11 | export const TYPEGOOSE_CONNECTION_NAME = 'TypegooseConnectionName'; 12 | 13 | /** 14 | * The provider name for the typegoose module options. 15 | * @internal 16 | */ 17 | export const TYPEGOOSE_MODULE_OPTIONS = 'TypegooseModuleOptions'; 18 | -------------------------------------------------------------------------------- /packages/web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@kalengo/web", 3 | "version": "1.0.3", 4 | "description": "aka web with koa", 5 | "scripts": { 6 | "test": "npm run test" 7 | }, 8 | "publishConfig": { 9 | "access": "public" 10 | }, 11 | "author": "nick", 12 | "license": "MIT", 13 | "dependencies": { 14 | "@kalengo/utils": "^1.0.3", 15 | "@nestjs/common": "7.0.8", 16 | "@nestjs/swagger": "4.5.1", 17 | "rxjs": "^6.5.5" 18 | }, 19 | "devDependencies": { 20 | "@types/glob": "^7.1.1" 21 | }, 22 | "gitHead": "4b2499c98f7b2334e390a18ab220547f7e00ee29" 23 | } 24 | -------------------------------------------------------------------------------- /sample/hello-world/config/production.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: process.env.PORT || 3000, 3 | mongodb: { 4 | debug: false, 5 | connections: [ 6 | { 7 | name: 'core', 8 | url: process.env.CORE_MONGODB, 9 | options: { 10 | useNewUrlParser: true, 11 | useUnifiedTopology: true 12 | } 13 | }, 14 | { 15 | name: 'app', 16 | url: process.env.APP_MONGODB, 17 | options: { 18 | useNewUrlParser: true, 19 | useUnifiedTopology: true 20 | } 21 | } 22 | ] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /sample/nest-simple/config/production.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: process.env.PORT || 3000, 3 | mongodb: { 4 | debug: false, 5 | connections: [ 6 | { 7 | name: 'core', 8 | url: process.env.CORE_MONGODB, 9 | options: { 10 | useNewUrlParser: true, 11 | useUnifiedTopology: true 12 | } 13 | }, 14 | { 15 | name: 'app', 16 | url: process.env.APP_MONGODB, 17 | options: { 18 | useNewUrlParser: true, 19 | useUnifiedTopology: true 20 | } 21 | } 22 | ] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /sample/nest-zeebe/config/production.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: process.env.PORT || 3000, 3 | mongodb: { 4 | debug: false, 5 | connections: [ 6 | { 7 | name: 'core', 8 | url: process.env.CORE_MONGODB, 9 | options: { 10 | useNewUrlParser: true, 11 | useUnifiedTopology: true 12 | } 13 | }, 14 | { 15 | name: 'app', 16 | url: process.env.APP_MONGODB, 17 | options: { 18 | useNewUrlParser: true, 19 | useUnifiedTopology: true 20 | } 21 | } 22 | ] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /sample/nest-zeebe/src/settings.ts: -------------------------------------------------------------------------------- 1 | import * as morgan from 'morgan' 2 | import { INestApplication } from '@nestjs/common' 3 | import { HttpExceptionFilter } from './common/filters/http-exception.filter' 4 | import { TransformInterceptor } from '@kalengo/web' 5 | 6 | export const prefix = 'api/v1' 7 | 8 | export function appSettings(app: INestApplication) { 9 | app.setGlobalPrefix(prefix) 10 | 11 | // request log 12 | app.use(morgan('tiny')) 13 | 14 | // 错误处理和返回值format 15 | app.useGlobalFilters(new HttpExceptionFilter()) 16 | app.useGlobalInterceptors(new TransformInterceptor()) 17 | } 18 | -------------------------------------------------------------------------------- /sample/hello-world/src/users/users.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { TypegooseModule } from 'nestjs-typegoose' 3 | import { UsersController } from './users.controller' 4 | import { UsersService } from './users.service' 5 | import { User } from './model/user.model' 6 | import { Account } from './model/account.model' 7 | 8 | @Module({ 9 | imports: [ 10 | TypegooseModule.forFeature([User], 'core'), 11 | TypegooseModule.forFeature([Account], 'app') 12 | ], 13 | controllers: [UsersController], 14 | providers: [UsersService] 15 | }) 16 | export class UsersModule {} 17 | -------------------------------------------------------------------------------- /sample/nest-ms-consumer/config/production.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: process.env.PORT || 3000, 3 | mongodb: { 4 | debug: false, 5 | connections: [ 6 | { 7 | name: 'core', 8 | url: process.env.CORE_MONGODB, 9 | options: { 10 | useNewUrlParser: true, 11 | useUnifiedTopology: true 12 | } 13 | }, 14 | { 15 | name: 'app', 16 | url: process.env.APP_MONGODB, 17 | options: { 18 | useNewUrlParser: true, 19 | useUnifiedTopology: true 20 | } 21 | } 22 | ] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /sample/nest-ms-provider/config/production.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: process.env.PORT || 3000, 3 | mongodb: { 4 | debug: false, 5 | connections: [ 6 | { 7 | name: 'core', 8 | url: process.env.CORE_MONGODB, 9 | options: { 10 | useNewUrlParser: true, 11 | useUnifiedTopology: true 12 | } 13 | }, 14 | { 15 | name: 'app', 16 | url: process.env.APP_MONGODB, 17 | options: { 18 | useNewUrlParser: true, 19 | useUnifiedTopology: true 20 | } 21 | } 22 | ] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /sample/redis-redlock-cache/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core' 2 | import { ApplicationModule } from './app.module' 3 | import { TransformInterceptor } from '@kalengo/web' 4 | 5 | async function bootstrap() { 6 | const app = await NestFactory.create(ApplicationModule) 7 | 8 | app.setGlobalPrefix('api/v1') 9 | 10 | // 错误处理和返回值format 11 | app.useGlobalInterceptors(new TransformInterceptor()) 12 | 13 | await app.listen(process.env.PORT || 3000) 14 | console.log( 15 | `Application(${process.env.NODE_ENV}) is running on: ${await app.getUrl()}` 16 | ) 17 | } 18 | 19 | bootstrap() 20 | -------------------------------------------------------------------------------- /deploy_doc.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 确保脚本抛出遇到的错误 4 | set -e 5 | 6 | # 生成静态文件 7 | npm run docs:build 8 | 9 | # 进入生成的文件夹 10 | cd docs/.vuepress/dist 11 | 12 | # 如果是发布到自定义域名 13 | # echo 'www.example.com' > CNAME 14 | 15 | git init 16 | git add -A 17 | git commit -m 'deploy' 18 | 19 | # 如果发布到 https://.github.io 20 | #git push -f git@github.com:kaolalicai/nest_doc.github.io.git master 21 | 22 | # 如果发布到 https://.github.io/ 23 | git push -f git@github.com:kaolalicai/nest_doc.github.io.git master:gh-pages 24 | git push -f git@github.com:kaolalicai/nest_doc.github.io.git master 25 | 26 | cd - 27 | -------------------------------------------------------------------------------- /sample/hello-world/config/dev.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: process.env.PORT || 3000, 3 | schedule: true, 4 | mongodb: { 5 | debug: true, 6 | connections: [ 7 | { 8 | name: 'core', 9 | url: 'mongodb://localhost:57017/core', 10 | options: { 11 | useNewUrlParser: true, 12 | useUnifiedTopology: true 13 | } 14 | }, 15 | { 16 | name: 'app', 17 | url: 'mongodb://localhost:57017/app', 18 | options: { 19 | useNewUrlParser: true, 20 | useUnifiedTopology: true 21 | } 22 | } 23 | ] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /sample/nest-simple/config/dev.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: process.env.PORT || 3000, 3 | schedule: true, 4 | mongodb: { 5 | debug: true, 6 | connections: [ 7 | { 8 | name: 'core', 9 | url: 'mongodb://localhost:57017/core', 10 | options: { 11 | useNewUrlParser: true, 12 | useUnifiedTopology: true 13 | } 14 | }, 15 | { 16 | name: 'app', 17 | url: 'mongodb://localhost:57017/app', 18 | options: { 19 | useNewUrlParser: true, 20 | useUnifiedTopology: true 21 | } 22 | } 23 | ] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /sample/nest-ms-consumer/config/dev.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: process.env.PORT || 3000, 3 | schedule: true, 4 | mongodb: { 5 | debug: true, 6 | connections: [ 7 | { 8 | name: 'core', 9 | url: 'mongodb://localhost:57017/core', 10 | options: { 11 | useNewUrlParser: true, 12 | useUnifiedTopology: true 13 | } 14 | }, 15 | { 16 | name: 'app', 17 | url: 'mongodb://localhost:57017/app', 18 | options: { 19 | useNewUrlParser: true, 20 | useUnifiedTopology: true 21 | } 22 | } 23 | ] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /sample/nest-ms-provider/config/dev.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: process.env.PORT || 3000, 3 | schedule: true, 4 | mongodb: { 5 | debug: true, 6 | connections: [ 7 | { 8 | name: 'core', 9 | url: 'mongodb://localhost:57017/core', 10 | options: { 11 | useNewUrlParser: true, 12 | useUnifiedTopology: true 13 | } 14 | }, 15 | { 16 | name: 'app', 17 | url: 'mongodb://localhost:57017/app', 18 | options: { 19 | useNewUrlParser: true, 20 | useUnifiedTopology: true 21 | } 22 | } 23 | ] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /sample/hello-world/test/main.ts: -------------------------------------------------------------------------------- 1 | import * as supertest from 'supertest' 2 | import { ApplicationModule } from '../src/app.module' 3 | import { Test } from '@nestjs/testing' 4 | import { appSettings } from '../src/settings' 5 | export { prefix } from '../src/settings' 6 | 7 | export async function bootstrap() { 8 | // init nestjs 9 | const testModule = await Test.createTestingModule({ 10 | imports: [ApplicationModule] 11 | }).compile() 12 | const app = testModule.createNestApplication() 13 | appSettings(app) 14 | await app.init() 15 | const request = supertest(app.getHttpServer()) 16 | return { app, request, testModule } 17 | } 18 | -------------------------------------------------------------------------------- /sample/nest-simple/test/main.ts: -------------------------------------------------------------------------------- 1 | import * as supertest from 'supertest' 2 | import { ApplicationModule } from '../src/app.module' 3 | import { Test } from '@nestjs/testing' 4 | import { appSettings } from '../src/settings' 5 | export { prefix } from '../src/settings' 6 | 7 | export async function bootstrap() { 8 | // init nestjs 9 | const testModule = await Test.createTestingModule({ 10 | imports: [ApplicationModule] 11 | }).compile() 12 | const app = testModule.createNestApplication() 13 | appSettings(app) 14 | await app.init() 15 | const request = supertest(app.getHttpServer()) 16 | return { app, request, testModule } 17 | } 18 | -------------------------------------------------------------------------------- /sample/nest-zeebe/src/model/order.model.ts: -------------------------------------------------------------------------------- 1 | import { prop, DocumentType, ReturnModelType } from '@typegoose/typegoose' 2 | 3 | export class Order { 4 | @prop({ required: true, index: true }) 5 | amount!: number 6 | 7 | @prop({ required: true, index: true, default: 'pending' }) 8 | status!: string 9 | 10 | async done(this: IOrderModel) { 11 | this.status = 'done' 12 | await this.save() 13 | } 14 | 15 | async fail(this: IOrderModel) { 16 | this.status = 'fail' 17 | await this.save() 18 | } 19 | } 20 | 21 | export type IOrderModel = DocumentType 22 | export type OrderModel = ReturnModelType 23 | -------------------------------------------------------------------------------- /sample/nest-zeebe/test/main.ts: -------------------------------------------------------------------------------- 1 | import * as supertest from 'supertest' 2 | import { ApplicationModule } from '../src/app.module' 3 | import { Test } from '@nestjs/testing' 4 | import { appSettings } from '../src/settings' 5 | export { prefix } from '../src/settings' 6 | 7 | export async function bootstrap() { 8 | // init nestjs 9 | const testModule = await Test.createTestingModule({ 10 | imports: [ApplicationModule] 11 | }).compile() 12 | const app = testModule.createNestApplication() 13 | appSettings(app) 14 | await app.init() 15 | const request = supertest(app.getHttpServer()) 16 | return { app, request, testModule } 17 | } 18 | -------------------------------------------------------------------------------- /sample/nest-ms-consumer/test/main.ts: -------------------------------------------------------------------------------- 1 | import * as supertest from 'supertest' 2 | import { ApplicationModule } from '../src/app.module' 3 | import { Test } from '@nestjs/testing' 4 | import { appSettings } from '../src/settings' 5 | export { prefix } from '../src/settings' 6 | 7 | export async function bootstrap() { 8 | // init nestjs 9 | const testModule = await Test.createTestingModule({ 10 | imports: [ApplicationModule] 11 | }).compile() 12 | const app = testModule.createNestApplication() 13 | appSettings(app) 14 | await app.init() 15 | const request = supertest(app.getHttpServer()) 16 | return { app, request, testModule } 17 | } 18 | -------------------------------------------------------------------------------- /sample/nest-ms-provider/test/main.ts: -------------------------------------------------------------------------------- 1 | import * as supertest from 'supertest' 2 | import { ApplicationModule } from '../src/app.module' 3 | import { Test } from '@nestjs/testing' 4 | import { appSettings } from '../src/settings' 5 | export { prefix } from '../src/settings' 6 | 7 | export async function bootstrap() { 8 | // init nestjs 9 | const testModule = await Test.createTestingModule({ 10 | imports: [ApplicationModule] 11 | }).compile() 12 | const app = testModule.createNestApplication() 13 | appSettings(app) 14 | await app.init() 15 | const request = supertest(app.getHttpServer()) 16 | return { app, request, testModule } 17 | } 18 | -------------------------------------------------------------------------------- /docs/config.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarDepth: 2 3 | --- 4 | 5 | ## 繁琐的 Nest Configuration 6 | Nest 的 Configuration 是异步的,这种设计导致在初始化 DB 连接的写法将会很繁琐 7 | 8 | ```ts 9 | MongooseModule.forRootAsync({ 10 | imports: [ConfigModule], 11 | useFactory: async (configService: ConfigService) => ({ 12 | uri: configService.getString('MONGODB_URI'), 13 | }), 14 | inject: [ConfigService], 15 | }); 16 | ``` 17 | 18 | ## 简单的 Config 19 | 基于我们的之前的开发经验, [config](https://www.npmjs.com/package/config) 这个包提供的配置功能已经足够好用, 20 | 所以在本脚手架中,我们将使用 config 来实现应用配置 21 | 22 | ```ts 23 | MongooseModule.forRootAsync({ 24 | useFactory: () => ({ 25 | uri: config.get('mongodb.uri') 26 | }), 27 | }); 28 | ``` 29 | -------------------------------------------------------------------------------- /sample/nest-keycloak/test/users/users-mock-auth.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { request } from '../test-helper' 2 | 3 | describe('users-mock-auth.e2e-spec.ts', () => { 4 | it('not login is ok ', () => { 5 | return request.get('/users/hello').expect(200) 6 | }) 7 | 8 | it('hello is public for all user', () => { 9 | return request.get('/users/hello').expect(200).expect({ 10 | code: 0, 11 | data: 'Hello World!', 12 | message: 'success' 13 | }) 14 | }) 15 | 16 | it('get user is ok', () => { 17 | return request.get('/users/info').expect(200) 18 | }) 19 | 20 | it('get all user is ok', () => { 21 | return request.get('/users/').expect(200) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /sample/nest-zeebe/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core' 2 | import { ApplicationModule } from './app.module' 3 | import { appSettings } from './settings' 4 | import { ZeebeServer } from '@payk/nestjs-zeebe' 5 | 6 | async function bootstrap() { 7 | const app = await NestFactory.create(ApplicationModule) 8 | 9 | appSettings(app) 10 | 11 | const microservice = app.connectMicroservice({ 12 | strategy: app.get(ZeebeServer) 13 | }) 14 | 15 | await app.startAllMicroservicesAsync() 16 | 17 | await app.listen(process.env.PORT || 3000) 18 | console.log( 19 | `Application(${process.env.NODE_ENV}) is running on: ${await app.getUrl()}` 20 | ) 21 | } 22 | 23 | bootstrap() 24 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin'], 8 | extends: [ 9 | 'plugin:@typescript-eslint/eslint-recommended', 10 | 'plugin:@typescript-eslint/recommended', 11 | 'prettier', 12 | 'prettier/@typescript-eslint', 13 | ], 14 | root: true, 15 | env: { 16 | node: true, 17 | jest: true, 18 | }, 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/no-explicit-any': 'off', 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /common/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin'], 8 | extends: [ 9 | 'plugin:@typescript-eslint/eslint-recommended', 10 | 'plugin:@typescript-eslint/recommended', 11 | 'prettier', 12 | 'prettier/@typescript-eslint', 13 | ], 14 | root: true, 15 | env: { 16 | node: true, 17 | jest: true, 18 | }, 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/no-explicit-any': 'off', 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /sample/redis-redlock-cache/test/test-helper.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common' 2 | import * as supertest from 'supertest' 3 | import { bootstrap } from './main' 4 | import { TestingModule } from '@nestjs/testing' 5 | 6 | process.env.NODE_ENV = 'test' 7 | console.log('current env', process.env.NODE_ENV) 8 | 9 | let app: INestApplication 10 | let testModule: TestingModule 11 | let request: supertest.SuperTest 12 | 13 | beforeAll(async function () { 14 | const res = await bootstrap() 15 | app = res.app 16 | request = res.request 17 | testModule = res.testModule 18 | }) 19 | 20 | afterAll(async () => { 21 | await app.close() 22 | }) 23 | 24 | export { app, request, testModule } 25 | -------------------------------------------------------------------------------- /sample/hello-world/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin'], 8 | extends: [ 9 | 'plugin:@typescript-eslint/eslint-recommended', 10 | 'plugin:@typescript-eslint/recommended', 11 | 'prettier', 12 | 'prettier/@typescript-eslint', 13 | ], 14 | root: true, 15 | env: { 16 | node: true, 17 | jest: true, 18 | }, 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/no-explicit-any': 'off', 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /sample/nest-keycloak/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin'], 8 | extends: [ 9 | 'plugin:@typescript-eslint/eslint-recommended', 10 | 'plugin:@typescript-eslint/recommended', 11 | 'prettier', 12 | 'prettier/@typescript-eslint', 13 | ], 14 | root: true, 15 | env: { 16 | node: true, 17 | jest: true, 18 | }, 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/no-explicit-any': 'off', 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /sample/nest-simple/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin'], 8 | extends: [ 9 | 'plugin:@typescript-eslint/eslint-recommended', 10 | 'plugin:@typescript-eslint/recommended', 11 | 'prettier', 12 | 'prettier/@typescript-eslint', 13 | ], 14 | root: true, 15 | env: { 16 | node: true, 17 | jest: true, 18 | }, 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/no-explicit-any': 'off', 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /sample/nest-zeebe/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin'], 8 | extends: [ 9 | 'plugin:@typescript-eslint/eslint-recommended', 10 | 'plugin:@typescript-eslint/recommended', 11 | 'prettier', 12 | 'prettier/@typescript-eslint', 13 | ], 14 | root: true, 15 | env: { 16 | node: true, 17 | jest: true, 18 | }, 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/no-explicit-any': 'off', 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /sample/nest-ms-consumer/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin'], 8 | extends: [ 9 | 'plugin:@typescript-eslint/eslint-recommended', 10 | 'plugin:@typescript-eslint/recommended', 11 | 'prettier', 12 | 'prettier/@typescript-eslint', 13 | ], 14 | root: true, 15 | env: { 16 | node: true, 17 | jest: true, 18 | }, 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/no-explicit-any': 'off', 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /sample/nest-ms-provider/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin'], 8 | extends: [ 9 | 'plugin:@typescript-eslint/eslint-recommended', 10 | 'plugin:@typescript-eslint/recommended', 11 | 'prettier', 12 | 'prettier/@typescript-eslint', 13 | ], 14 | root: true, 15 | env: { 16 | node: true, 17 | jest: true, 18 | }, 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/no-explicit-any': 'off', 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /sample/redis-redlock-cache/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin'], 8 | extends: [ 9 | 'plugin:@typescript-eslint/eslint-recommended', 10 | 'plugin:@typescript-eslint/recommended', 11 | 'prettier', 12 | 'prettier/@typescript-eslint', 13 | ], 14 | root: true, 15 | env: { 16 | node: true, 17 | jest: true, 18 | }, 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/no-explicit-any': 'off', 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /sample/hello-world/config/default.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: process.env.PORT || 3000, 3 | mongodb: { 4 | debug: false, 5 | connections: [ 6 | { 7 | name: 'core', 8 | url: process.env.CORE_MONGODB, 9 | options: { 10 | useNewUrlParser: true, 11 | useUnifiedTopology: true 12 | } 13 | }, 14 | { 15 | name: 'app', 16 | url: process.env.APP_MONGODB, 17 | options: { 18 | useNewUrlParser: true, 19 | useUnifiedTopology: true 20 | } 21 | } 22 | ] 23 | }, 24 | redis: { 25 | uri: process.env.REDIS_URI || 'redis://localhost:6379', 26 | prefix: process.env.REDIS_PREFIX || 'redis_' 27 | }, 28 | } 29 | -------------------------------------------------------------------------------- /sample/nest-keycloak/config/default.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: process.env.PORT || 3000, 3 | mongodb: { 4 | debug: false, 5 | connections: [ 6 | { 7 | name: 'core', 8 | url: process.env.CORE_MONGODB, 9 | options: { 10 | useNewUrlParser: true, 11 | useUnifiedTopology: true 12 | } 13 | }, 14 | { 15 | name: 'app', 16 | url: process.env.APP_MONGODB, 17 | options: { 18 | useNewUrlParser: true, 19 | useUnifiedTopology: true 20 | } 21 | } 22 | ] 23 | }, 24 | redis: { 25 | uri: process.env.REDIS_URI || 'redis://localhost:6379', 26 | prefix: process.env.REDIS_PREFIX || 'redis_' 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /sample/nest-simple/config/default.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: process.env.PORT || 3000, 3 | mongodb: { 4 | debug: false, 5 | connections: [ 6 | { 7 | name: 'core', 8 | url: process.env.CORE_MONGODB, 9 | options: { 10 | useNewUrlParser: true, 11 | useUnifiedTopology: true 12 | } 13 | }, 14 | { 15 | name: 'app', 16 | url: process.env.APP_MONGODB, 17 | options: { 18 | useNewUrlParser: true, 19 | useUnifiedTopology: true 20 | } 21 | } 22 | ] 23 | }, 24 | redis: { 25 | uri: process.env.REDIS_URI || 'redis://localhost:6379', 26 | prefix: process.env.REDIS_PREFIX || 'redis_' 27 | }, 28 | } 29 | -------------------------------------------------------------------------------- /sample/nest-zeebe/config/default.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: process.env.PORT || 3000, 3 | mongodb: { 4 | debug: false, 5 | connections: [ 6 | { 7 | name: 'core', 8 | url: process.env.CORE_MONGODB, 9 | options: { 10 | useNewUrlParser: true, 11 | useUnifiedTopology: true 12 | } 13 | }, 14 | { 15 | name: 'app', 16 | url: process.env.APP_MONGODB, 17 | options: { 18 | useNewUrlParser: true, 19 | useUnifiedTopology: true 20 | } 21 | } 22 | ] 23 | }, 24 | redis: { 25 | uri: process.env.REDIS_URI || 'redis://localhost:6379', 26 | prefix: process.env.REDIS_PREFIX || 'redis_' 27 | }, 28 | } 29 | -------------------------------------------------------------------------------- /packages/web/src/transform.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | NestInterceptor, 4 | CallHandler, 5 | ExecutionContext 6 | } from '@nestjs/common' 7 | import { map } from 'rxjs/operators' 8 | import { Observable } from 'rxjs' 9 | 10 | interface Response { 11 | data: T 12 | } 13 | 14 | @Injectable() 15 | export class TransformInterceptor 16 | implements NestInterceptor> { 17 | intercept( 18 | context: ExecutionContext, 19 | next: CallHandler 20 | ): Observable> { 21 | return next.handle().pipe( 22 | map((data) => { 23 | return { 24 | data, 25 | code: 0, 26 | message: 'success' 27 | } 28 | }) 29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /sample/nest-ms-consumer/config/default.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: process.env.PORT || 3000, 3 | mongodb: { 4 | debug: false, 5 | connections: [ 6 | { 7 | name: 'core', 8 | url: process.env.CORE_MONGODB, 9 | options: { 10 | useNewUrlParser: true, 11 | useUnifiedTopology: true 12 | } 13 | }, 14 | { 15 | name: 'app', 16 | url: process.env.APP_MONGODB, 17 | options: { 18 | useNewUrlParser: true, 19 | useUnifiedTopology: true 20 | } 21 | } 22 | ] 23 | }, 24 | redis: { 25 | uri: process.env.REDIS_URI || 'redis://localhost:6379', 26 | prefix: process.env.REDIS_PREFIX || 'redis_' 27 | }, 28 | } 29 | -------------------------------------------------------------------------------- /sample/nest-ms-provider/config/default.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: process.env.PORT || 3000, 3 | mongodb: { 4 | debug: false, 5 | connections: [ 6 | { 7 | name: 'core', 8 | url: process.env.CORE_MONGODB, 9 | options: { 10 | useNewUrlParser: true, 11 | useUnifiedTopology: true 12 | } 13 | }, 14 | { 15 | name: 'app', 16 | url: process.env.APP_MONGODB, 17 | options: { 18 | useNewUrlParser: true, 19 | useUnifiedTopology: true 20 | } 21 | } 22 | ] 23 | }, 24 | redis: { 25 | uri: process.env.REDIS_URI || 'redis://localhost:6379', 26 | prefix: process.env.REDIS_PREFIX || 'redis_' 27 | }, 28 | } 29 | -------------------------------------------------------------------------------- /sample/nest-zeebe/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { TypegooseModuleBuilder } from '@kalengo/mongoose' 3 | import { ZeebeModule, ZeebeServer } from '@payk/nestjs-zeebe' 4 | import { AppController } from './app.controller' 5 | import { RechargeService } from './service/recharge.service' 6 | import { Order } from './model/order.model' 7 | import { TypegooseModule } from 'nestjs-typegoose' 8 | 9 | @Module({ 10 | imports: [ 11 | TypegooseModuleBuilder.forRoot(), 12 | ZeebeModule.forRoot({ gatewayAddress: 'localhost:26500' }), 13 | TypegooseModule.forFeature([Order]) 14 | ], 15 | controllers: [AppController], 16 | providers: [ZeebeServer, RechargeService] 17 | }) 18 | export class ApplicationModule {} 19 | -------------------------------------------------------------------------------- /packages/mongoose/src/typegoose/typegoose.utils.ts: -------------------------------------------------------------------------------- 1 | import { DEFAULT_DB_CONNECTION_NAME } from './typegoose.constants'; 2 | 3 | /** 4 | * Returns the provider token name. 5 | * @param model The model name 6 | * @returns The token name 7 | * @internal 8 | */ 9 | export function getModelToken(model: string) { 10 | return `${model}Model`; 11 | } 12 | 13 | /** 14 | * Returns the provider connection token name. 15 | * @param name the name of the connection 16 | * @returns the connection provider name 17 | * @internal 18 | */ 19 | export function getConnectionToken(name?: string) { 20 | if (typeof name === 'string' && name !== DEFAULT_DB_CONNECTION_NAME) { 21 | return `${name}Connection`; 22 | } 23 | return DEFAULT_DB_CONNECTION_NAME; 24 | } 25 | -------------------------------------------------------------------------------- /sample/nest-keycloak/test/test-helper.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common' 2 | import * as supertest from 'supertest' 3 | import { bootstrap, prefix as pre } from './main' 4 | import { TestingModule } from '@nestjs/testing' 5 | 6 | process.env.NODE_ENV = 'test' 7 | console.log('current env', process.env.NODE_ENV) 8 | 9 | let app: INestApplication 10 | let testModule: TestingModule 11 | let request: supertest.SuperTest 12 | const prefix = '/' + pre 13 | 14 | beforeAll(async function () { 15 | const res = await bootstrap() 16 | app = res.app 17 | request = res.request 18 | testModule = res.testModule 19 | }) 20 | 21 | afterAll(async () => { 22 | await app.close() 23 | }) 24 | export { app, request, testModule, prefix } 25 | -------------------------------------------------------------------------------- /sample/nest-ms-consumer/src/users/users.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { UsersController } from './users.controller' 3 | import { UsersService } from './users.service' 4 | import { ClientsModule, Transport } from '@nestjs/microservices' 5 | 6 | @Module({ 7 | imports: [ 8 | ClientsModule.register([ 9 | { 10 | name: 'MATH_SERVICE', 11 | transport: Transport.RMQ, 12 | options: { 13 | urls: ['amqp://localhost:5672'], 14 | queue: 'ms_queue', 15 | queueOptions: { 16 | durable: false 17 | } 18 | } 19 | } 20 | ]) 21 | ], 22 | controllers: [UsersController], 23 | providers: [UsersService] 24 | }) 25 | export class UsersModule {} 26 | -------------------------------------------------------------------------------- /sample/nest-simple/test/test-helper.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common' 2 | import * as supertest from 'supertest' 3 | import { bootstrap, prefix as pre } from './main' 4 | import { TestingModule } from '@nestjs/testing' 5 | 6 | process.env.NODE_ENV = 'test' 7 | console.log('current env', process.env.NODE_ENV) 8 | 9 | let app: INestApplication 10 | let testModule: TestingModule 11 | let request: supertest.SuperTest 12 | const prefix = '/' + pre 13 | 14 | beforeAll(async function () { 15 | const res = await bootstrap() 16 | app = res.app 17 | request = res.request 18 | testModule = res.testModule 19 | }) 20 | 21 | afterAll(async () => { 22 | await app.close() 23 | }) 24 | export { app, request, testModule, prefix } 25 | -------------------------------------------------------------------------------- /common/README.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 4 | 5 | Nest 脚手架,在开始使用之前,你需要对 Nest 有个基本的认识,特别是 Module 的概念。 6 | 7 | 本脚手架是 Kalengo 适配版本,针对 Kalengo 的实际需求对 Nest 做了适配. 8 | 9 | 详细内容见文档: [Kalengo Nest Starter](https://kaolalicai.github.io/nest_doc/) 10 | 11 | ## Installation 12 | 13 | ```bash 14 | $ npm install 15 | ``` 16 | 17 | ## Running the app 18 | 19 | ```bash 20 | # development 21 | $ npm run start 22 | 23 | # watch mode 24 | $ npm run start:dev 25 | 26 | # production mode 27 | $ npm run start:prod 28 | ``` 29 | 30 | ## Test 31 | 32 | ```bash 33 | # unit tests 34 | $ npm run test 35 | 36 | # e2e tests 37 | $ npm run test:e2e 38 | 39 | # test coverage 40 | $ npm run test:cov 41 | ``` 42 | -------------------------------------------------------------------------------- /sample/nest-ms-consumer/test/test-helper.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common' 2 | import * as supertest from 'supertest' 3 | import { bootstrap, prefix as pre } from './main' 4 | import { TestingModule } from '@nestjs/testing' 5 | 6 | process.env.NODE_ENV = 'test' 7 | console.log('current env', process.env.NODE_ENV) 8 | 9 | let app: INestApplication 10 | let testModule: TestingModule 11 | let request: supertest.SuperTest 12 | const prefix = '/' + pre 13 | 14 | beforeAll(async function () { 15 | const res = await bootstrap() 16 | app = res.app 17 | request = res.request 18 | testModule = res.testModule 19 | }) 20 | 21 | afterAll(async () => { 22 | await app.close() 23 | }) 24 | export { app, request, testModule, prefix } 25 | -------------------------------------------------------------------------------- /sample/nest-ms-provider/test/test-helper.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common' 2 | import * as supertest from 'supertest' 3 | import { bootstrap, prefix as pre } from './main' 4 | import { TestingModule } from '@nestjs/testing' 5 | 6 | process.env.NODE_ENV = 'test' 7 | console.log('current env', process.env.NODE_ENV) 8 | 9 | let app: INestApplication 10 | let testModule: TestingModule 11 | let request: supertest.SuperTest 12 | const prefix = '/' + pre 13 | 14 | beforeAll(async function () { 15 | const res = await bootstrap() 16 | app = res.app 17 | request = res.request 18 | testModule = res.testModule 19 | }) 20 | 21 | afterAll(async () => { 22 | await app.close() 23 | }) 24 | export { app, request, testModule, prefix } 25 | -------------------------------------------------------------------------------- /sample/hello-world/README.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 4 | 5 | Nest 脚手架,在开始使用之前,你需要对 Nest 有个基本的认识,特别是 Module 的概念。 6 | 7 | 本脚手架是 Kalengo 适配版本,针对 Kalengo 的实际需求对 Nest 做了适配. 8 | 9 | 详细内容见文档: [Kalengo Nest Starter](https://kaolalicai.github.io/nest_doc/) 10 | 11 | ## Installation 12 | 13 | ```bash 14 | $ npm install 15 | ``` 16 | 17 | ## Running the app 18 | 19 | ```bash 20 | # development 21 | $ npm run start 22 | 23 | # watch mode 24 | $ npm run start:dev 25 | 26 | # production mode 27 | $ npm run start:prod 28 | ``` 29 | 30 | ## Test 31 | 32 | ```bash 33 | # unit tests 34 | $ npm run test 35 | 36 | # e2e tests 37 | $ npm run test:e2e 38 | 39 | # test coverage 40 | $ npm run test:cov 41 | ``` 42 | -------------------------------------------------------------------------------- /sample/nest-simple/README.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 4 | 5 | Nest 脚手架,在开始使用之前,你需要对 Nest 有个基本的认识,特别是 Module 的概念。 6 | 7 | 本脚手架是 Kalengo 适配版本,针对 Kalengo 的实际需求对 Nest 做了适配. 8 | 9 | 详细内容见文档: [Kalengo Nest Starter](https://kaolalicai.github.io/nest_doc/) 10 | 11 | ## Installation 12 | 13 | ```bash 14 | $ npm install 15 | ``` 16 | 17 | ## Running the app 18 | 19 | ```bash 20 | # development 21 | $ npm run start 22 | 23 | # watch mode 24 | $ npm run start:dev 25 | 26 | # production mode 27 | $ npm run start:prod 28 | ``` 29 | 30 | ## Test 31 | 32 | ```bash 33 | # unit tests 34 | $ npm run test 35 | 36 | # e2e tests 37 | $ npm run test:e2e 38 | 39 | # test coverage 40 | $ npm run test:cov 41 | ``` 42 | -------------------------------------------------------------------------------- /sample/nest-zeebe/README.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 4 | 5 | Nest 脚手架,在开始使用之前,你需要对 Nest 有个基本的认识,特别是 Module 的概念。 6 | 7 | 本脚手架是 Kalengo 适配版本,针对 Kalengo 的实际需求对 Nest 做了适配. 8 | 9 | 详细内容见文档: [Kalengo Nest Starter](https://kaolalicai.github.io/nest_doc/) 10 | 11 | ## Installation 12 | 13 | ```bash 14 | $ npm install 15 | ``` 16 | 17 | ## Running the app 18 | 19 | ```bash 20 | # development 21 | $ npm run start 22 | 23 | # watch mode 24 | $ npm run start:dev 25 | 26 | # production mode 27 | $ npm run start:prod 28 | ``` 29 | 30 | ## Test 31 | 32 | ```bash 33 | # unit tests 34 | $ npm run test 35 | 36 | # e2e tests 37 | $ npm run test:e2e 38 | 39 | # test coverage 40 | $ npm run test:cov 41 | ``` 42 | -------------------------------------------------------------------------------- /packages/web/src/validation.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, ValidationPipe, ValidationError } from '@nestjs/common' 2 | import { HttpErrorByCode } from '@nestjs/common/utils/http-error-by-code.util' 3 | 4 | @Injectable() 5 | export class ParamsValidationPipe extends ValidationPipe { 6 | public createExceptionFactory() { 7 | return (validationErrors: ValidationError[] = []) => { 8 | if (this.isDetailedOutputDisabled) { 9 | return new HttpErrorByCode[this.errorHttpStatusCode]() 10 | } 11 | // eslint-disable-next-line @typescript-eslint/ban-ts-ignore 12 | // @ts-ignore 13 | const errors = this.flattenValidationErrors(validationErrors) 14 | return new HttpErrorByCode[this.errorHttpStatusCode](errors.join(';')) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /sample/nest-keycloak/README.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 4 | 5 | Nest 脚手架,在开始使用之前,你需要对 Nest 有个基本的认识,特别是 Module 的概念。 6 | 7 | 本脚手架是 Kalengo 适配版本,针对 Kalengo 的实际需求对 Nest 做了适配. 8 | 9 | 详细内容见文档: [Kalengo Nest Starter](https://kaolalicai.github.io/nest_doc/) 10 | 11 | ## Installation 12 | 13 | ```bash 14 | $ npm install 15 | ``` 16 | 17 | ## Running the app 18 | 19 | ```bash 20 | # development 21 | $ npm run start 22 | 23 | # watch mode 24 | $ npm run start:dev 25 | 26 | # production mode 27 | $ npm run start:prod 28 | ``` 29 | 30 | ## Test 31 | 32 | ```bash 33 | # unit tests 34 | $ npm run test 35 | 36 | # e2e tests 37 | $ npm run test:e2e 38 | 39 | # test coverage 40 | $ npm run test:cov 41 | ``` 42 | -------------------------------------------------------------------------------- /sample/nest-ms-consumer/README.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 4 | 5 | Nest 脚手架,在开始使用之前,你需要对 Nest 有个基本的认识,特别是 Module 的概念。 6 | 7 | 本脚手架是 Kalengo 适配版本,针对 Kalengo 的实际需求对 Nest 做了适配. 8 | 9 | 详细内容见文档: [Kalengo Nest Starter](https://kaolalicai.github.io/nest_doc/) 10 | 11 | ## Installation 12 | 13 | ```bash 14 | $ npm install 15 | ``` 16 | 17 | ## Running the app 18 | 19 | ```bash 20 | # development 21 | $ npm run start 22 | 23 | # watch mode 24 | $ npm run start:dev 25 | 26 | # production mode 27 | $ npm run start:prod 28 | ``` 29 | 30 | ## Test 31 | 32 | ```bash 33 | # unit tests 34 | $ npm run test 35 | 36 | # e2e tests 37 | $ npm run test:e2e 38 | 39 | # test coverage 40 | $ npm run test:cov 41 | ``` 42 | -------------------------------------------------------------------------------- /sample/nest-ms-provider/README.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 4 | 5 | Nest 脚手架,在开始使用之前,你需要对 Nest 有个基本的认识,特别是 Module 的概念。 6 | 7 | 本脚手架是 Kalengo 适配版本,针对 Kalengo 的实际需求对 Nest 做了适配. 8 | 9 | 详细内容见文档: [Kalengo Nest Starter](https://kaolalicai.github.io/nest_doc/) 10 | 11 | ## Installation 12 | 13 | ```bash 14 | $ npm install 15 | ``` 16 | 17 | ## Running the app 18 | 19 | ```bash 20 | # development 21 | $ npm run start 22 | 23 | # watch mode 24 | $ npm run start:dev 25 | 26 | # production mode 27 | $ npm run start:prod 28 | ``` 29 | 30 | ## Test 31 | 32 | ```bash 33 | # unit tests 34 | $ npm run test 35 | 36 | # e2e tests 37 | $ npm run test:e2e 38 | 39 | # test coverage 40 | $ npm run test:cov 41 | ``` 42 | -------------------------------------------------------------------------------- /sample/redis-redlock-cache/README.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 4 | 5 | Nest 脚手架,在开始使用之前,你需要对 Nest 有个基本的认识,特别是 Module 的概念。 6 | 7 | 本脚手架是 Kalengo 适配版本,针对 Kalengo 的实际需求对 Nest 做了适配. 8 | 9 | 详细内容见文档: [Kalengo Nest Starter](https://kaolalicai.github.io/nest_doc/) 10 | 11 | ## Installation 12 | 13 | ```bash 14 | $ npm install 15 | ``` 16 | 17 | ## Running the app 18 | 19 | ```bash 20 | # development 21 | $ npm run start 22 | 23 | # watch mode 24 | $ npm run start:dev 25 | 26 | # production mode 27 | $ npm run start:prod 28 | ``` 29 | 30 | ## Test 31 | 32 | ```bash 33 | # unit tests 34 | $ npm run test 35 | 36 | # e2e tests 37 | $ npm run test:e2e 38 | 39 | # test coverage 40 | $ npm run test:cov 41 | ``` 42 | -------------------------------------------------------------------------------- /packages/redis/src/ioredis/redis.module.builder.ts: -------------------------------------------------------------------------------- 1 | import { DynamicModule, Module, Logger } from '@nestjs/common' 2 | import { RedisModule } from 'nestjs-redis' 3 | import { parseConfig } from '../config.parse' 4 | 5 | async function onClientReady(client) { 6 | client.on('error', (err) => { 7 | Logger.log('redis error', err) 8 | }) 9 | } 10 | 11 | @Module({}) 12 | export class RedisModuleBuilder { 13 | static forRoot(): DynamicModule { 14 | const { redisConfig } = parseConfig() 15 | const connections = [] 16 | const con = RedisModule.register({ 17 | url: redisConfig.uri, 18 | onClientReady: onClientReady 19 | }) 20 | connections.push(con) 21 | return { 22 | module: RedisModuleBuilder, 23 | imports: connections 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tools/gulp/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": true, 4 | "noUnusedParameters": false, 5 | "noUnusedLocals": false, 6 | "lib": [ 7 | "es2015", 8 | "dom", 9 | "es2016.array.include" 10 | ], 11 | "module": "commonjs", 12 | "moduleResolution": "node", 13 | "outDir": "../../dist/tools/gulp", 14 | "strictNullChecks": true, 15 | "strictFunctionTypes": true, 16 | "noImplicitThis": true, 17 | "noEmitOnError": true, 18 | "noImplicitAny": false, 19 | "target": "es5", 20 | "types": [ 21 | "node" 22 | ], 23 | "typeRoots": [ 24 | "./typings", 25 | "../../node_modules/@types/" 26 | ], 27 | "baseUrl": "." 28 | }, 29 | "files": [ 30 | "gulpfile.ts" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /docs/quickstart.md: -------------------------------------------------------------------------------- 1 | # QuickStart 2 | 3 | ## 初始化 4 | 我们提供了一个脚手架模板,使用 klg-init 工具来初始化项目,如果没有 klg-init, 请先安装 5 | 6 | ```bash 7 | npm i klg-init -g 8 | ``` 9 | 10 | 创建项目 11 | 12 | ```bash 13 | $ mkdir nest-example && cd nest-example 14 | $ klg-init . --type=nest 15 | $ npm i 16 | ``` 17 | 18 | ## 启动项目 19 | 20 | ```bash 21 | # development watch mode 22 | $ npm run start:dev 23 | 24 | # production mode 25 | $ npm run start 26 | ``` 27 | 28 | open http://localhost:3000 29 | 30 | ## 测试Test 31 | 32 | 33 | ### 单元测试 34 | 35 | ```bash 36 | $ npm run test 37 | ``` 38 | 39 | ### e2e 测试 40 | e2e 测试依赖 mongodb,开始之前请修改 config/test.js 里的 mongodb uri 41 | 42 | ```bash 43 | $ npm run test 44 | ``` 45 | 46 | ### 测试覆盖率(TODO) 47 | 48 | ```bash 49 | # test coverage 50 | $ npm run test:cov 51 | ``` 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /sample/hello-world/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { UsersModule } from './users/users.module' 3 | import { TypegooseModuleBuilder } from '@kalengo/mongoose' 4 | import { APP_INTERCEPTOR, APP_FILTER, APP_PIPE } from '@nestjs/core' 5 | import { 6 | TransformInterceptor, 7 | HttpExceptionFilter, 8 | ParamsValidationPipe 9 | } from '@kalengo/web' 10 | 11 | @Module({ 12 | imports: [TypegooseModuleBuilder.forRoot(), UsersModule], 13 | providers: [ 14 | { 15 | provide: APP_FILTER, 16 | useClass: HttpExceptionFilter 17 | }, 18 | { 19 | provide: APP_INTERCEPTOR, 20 | useClass: TransformInterceptor 21 | }, 22 | { 23 | provide: APP_PIPE, 24 | useClass: ParamsValidationPipe 25 | } 26 | ] 27 | }) 28 | export class ApplicationModule {} 29 | -------------------------------------------------------------------------------- /docs/extend/ms.md: -------------------------------------------------------------------------------- 1 | Nest 官方文档有对[微服务](https://docs.nestjs.cn/7/microservices)有详细的描述 2 | 3 | 实际上 Nest 所提供的并未全功能的微服务,应该说只是提供了**跨服务调用机制** 4 | 5 | 如果我们需要解决更多的微服务问题,例如服务注册与发现,熔断限流,分布式追踪等,需要自己采用第三方框架来实现。 6 | 7 | 接下来介绍一下,Nest 微服务提供的各种传输层方案的适用场景 8 | 9 | - TCP: 最简单的模式, 适合开发模式,不好做负载均衡 10 | - Redis: 不能保证消息一定会被消费 11 | - MQTT: 适合高延迟场景,例如智能家居 12 | - NATS: 简单,高性能,国内社区不活跃 13 | - RabbitMQ: 稳,适合对一致性要求高的场景,例如核心交易业务 14 | - kafka: 快,适合大量数据传输的场景,例如日志上传 15 | - gRPC: 对 TCP 协议的升级,跨语言,同样需要额外做负载均衡,适合 k8s 环境 16 | 17 | 请各位根据自己业务的实际情况选择传输层, 注意 [混合应用](https://docs.nestjs.cn/7/faq?id=%e6%b7%b7%e5%90%88%e5%ba%94%e7%94%a8) 的写法,文档藏得比较深 18 | 19 | 微服务的完整例子见: 20 | 21 | [Provider 提供者](https://github.com/kaolalicai/klg-nest-starter/tree/master/sample/nest-ms-provider) 22 | 23 | [Consumer 消费者](https://github.com/kaolalicai/klg-nest-starter/tree/master/sample/nest-ms-consumer) 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /sample/nest-ms-provider/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core' 2 | import { ApplicationModule } from './app.module' 3 | import { appSettings } from './settings' 4 | import { Transport, MicroserviceOptions } from '@nestjs/microservices' 5 | 6 | async function bootstrap() { 7 | const app = await NestFactory.create(ApplicationModule) 8 | const microservice = app.connectMicroservice({ 9 | transport: Transport.RMQ, 10 | options: { 11 | urls: ['amqp://localhost:5672'], 12 | queue: 'ms_queue', 13 | queueOptions: { 14 | durable: false 15 | } 16 | } 17 | }) 18 | appSettings(app) 19 | 20 | await app.startAllMicroservicesAsync() 21 | await app.listen(process.env.PORT || 3002) 22 | console.log( 23 | `Application(${process.env.NODE_ENV}) is running on: ${await app.getUrl()}` 24 | ) 25 | } 26 | 27 | bootstrap() 28 | -------------------------------------------------------------------------------- /sample/hello-world/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core' 2 | import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger' 3 | import { ApplicationModule } from './app.module' 4 | import { appSettings } from './settings' 5 | 6 | async function bootstrap() { 7 | const app = await NestFactory.create(ApplicationModule) 8 | 9 | appSettings(app) 10 | 11 | // Swagger 12 | const options = new DocumentBuilder() 13 | .setTitle('Nest Starter') 14 | .setDescription('The Server API description') 15 | .setVersion('1.0') 16 | .addTag('starter') 17 | .build() 18 | const document = SwaggerModule.createDocument(app, options) 19 | SwaggerModule.setup('api', app, document) 20 | 21 | await app.listen(process.env.PORT || 3000) 22 | console.log( 23 | `Application(${process.env.NODE_ENV}) is running on: ${await app.getUrl()}` 24 | ) 25 | } 26 | 27 | bootstrap() 28 | -------------------------------------------------------------------------------- /packages/mongoose/src/typegoose.module.builder.ts: -------------------------------------------------------------------------------- 1 | import { DynamicModule, Module } from '@nestjs/common' 2 | import * as mongoose from 'mongoose' 3 | import {TypegooseModule} from './typegoose' 4 | import { parseConfig } from './config.parse' 5 | 6 | @Module({}) 7 | export class TypegooseModuleBuilder { 8 | static forRoot(): DynamicModule { 9 | const { mongoConfigs, debugMongoose } = parseConfig() 10 | 11 | mongoose.set('debug', debugMongoose) 12 | 13 | const connections = [] 14 | for (const c of mongoConfigs) { 15 | const typegooseModule = TypegooseModule.forRoot( 16 | c.url, 17 | Object.assign({ connectionName: c.name }, c.options) 18 | ) 19 | // connections.push(...typegooseModule.imports) 20 | connections.push(typegooseModule) 21 | } 22 | return { 23 | module: TypegooseModuleBuilder, 24 | imports: connections 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /sample/hello-world/src/users/users.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty } from 'class-validator' 2 | import { ApiProperty } from '@nestjs/swagger' 3 | import { BaseResponse } from '@kalengo/web' 4 | 5 | export class UserDto { 6 | @IsNotEmpty() 7 | @ApiProperty() 8 | readonly name!: string 9 | @IsNotEmpty() 10 | @ApiProperty() 11 | readonly phone!: string 12 | } 13 | 14 | export class AccountDto { 15 | @IsNotEmpty() 16 | @ApiProperty() 17 | userId!: string 18 | 19 | @ApiProperty() 20 | balance!: number 21 | } 22 | 23 | export class RegisterRes extends BaseResponse { 24 | @ApiProperty({ type: UserDto }) 25 | readonly data!: UserDto 26 | } 27 | 28 | export class FindAccountRes extends BaseResponse { 29 | @ApiProperty({ type: AccountDto }) 30 | readonly data!: AccountDto 31 | } 32 | 33 | export class FindUsersRes extends BaseResponse { 34 | @ApiProperty({ type: [AccountDto] }) 35 | readonly data!: AccountDto[] 36 | } 37 | -------------------------------------------------------------------------------- /sample/redis-redlock-cache/src/users/users.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty } from 'class-validator' 2 | import { ApiProperty } from '@nestjs/swagger' 3 | import { BaseResponse } from '@kalengo/web' 4 | 5 | export class UserDto { 6 | @IsNotEmpty() 7 | @ApiProperty() 8 | readonly name!: string 9 | @IsNotEmpty() 10 | @ApiProperty() 11 | readonly phone!: string 12 | } 13 | 14 | export class AccountDto { 15 | @IsNotEmpty() 16 | @ApiProperty() 17 | userId!: string 18 | 19 | @ApiProperty() 20 | balance!: number 21 | } 22 | 23 | export class RegisterRes extends BaseResponse { 24 | @ApiProperty({ type: UserDto }) 25 | readonly data!: UserDto 26 | } 27 | 28 | export class FindAccountRes extends BaseResponse { 29 | @ApiProperty({ type: AccountDto }) 30 | readonly data!: AccountDto 31 | } 32 | 33 | export class FindUsersRes extends BaseResponse { 34 | @ApiProperty({ type: [AccountDto] }) 35 | readonly data!: AccountDto[] 36 | } 37 | -------------------------------------------------------------------------------- /docs/utils.md: -------------------------------------------------------------------------------- 1 | 我们会把常用的工具集放入 npm 包中,方便集中维护, 所有工具集都会放入 @kalengo 这个域中 2 | 3 | @kalengo/utils 收集了 Kalengo 后端开发常用的工具类,目前有 4 | 5 | ## DateUtil 6 | 日期计算,节假日 7 | 8 | TODO: 兼容多时区 9 | 10 | ## NumberUtil 11 | 主要处理 0.1 + 0.2 = 0.30000000000000004 问题 12 | 13 | ## Logger 14 | logger 工具,小巧玲珑,基于开源日志工具 [tracer](https://github.com/baryon/tracer), 这个库的优势是可以打印 log 发生的文件位置。 15 | 16 | 接下来介绍一些常用配置,配置写在 config/xxx.js 中 17 | 18 | 配置日志级别,具体有那些级别请看 tracer 文档 19 | ```ts 20 | log: { 21 | level: 'info' 22 | } 23 | ``` 24 | 25 | 把日志写入文件中 26 | 27 | ```ts 28 | log: { 29 | level: 'info', 30 | root: './logs', 31 | allLogsFileName: 'mongoose' 32 | } 33 | ``` 34 | root 就是文件保存的路径。 allLogsFileName 是文件名。 日志默认会按日分割。 35 | 36 | 如果你需要自定义的 logger,直接用新的 config 构造一个 logger 就行。 37 | 38 | ```ts 39 | import { LoggerFactory } from '@akajs/utils' 40 | const logger = LoggerFactory(config) 41 | 42 | export {logger} 43 | ``` 44 | config 写入你自定义的配置 45 | 46 | ## StringUtil(TODO) 47 | 48 | -------------------------------------------------------------------------------- /tools/gulp/tasks/copy-misc.ts: -------------------------------------------------------------------------------- 1 | import {task, src, dest, series} from 'gulp' 2 | import {getDirs} from '../util/task-helpers' 3 | import {samplePath, packagesPath} from '../config' 4 | 5 | /** 6 | * Moves the base config filesfiles into the 7 | * `samples/*` dirs. 8 | */ 9 | function copyMiscToSample () { 10 | const directories = getDirs(samplePath) 11 | const distFiles = src(['./common/*', './common/.*']) 12 | return directories.reduce( 13 | (distFile, dir) => { 14 | return distFile.pipe(dest(dir)) 15 | }, 16 | distFiles 17 | ) 18 | } 19 | 20 | function copyMiscToPackages () { 21 | const directories = getDirs(packagesPath) 22 | const distFiles = src(['./packages/Readme.md', './packages/.npmignore']) 23 | return directories.reduce( 24 | (distFile, dir) => { 25 | return distFile.pipe(dest(dir)) 26 | }, 27 | distFiles 28 | ) 29 | } 30 | 31 | task('copy-misc', series(copyMiscToSample, copyMiscToPackages)) 32 | -------------------------------------------------------------------------------- /packages/mongoose/src/typegoose/typegoose-options.interface.ts: -------------------------------------------------------------------------------- 1 | import { Type } from '@nestjs/common'; 2 | import { ModuleMetadata } from '@nestjs/common/interfaces'; 3 | import { ConnectOptions } from 'mongoose'; 4 | 5 | export interface TypegooseConnectionOptions extends ConnectOptions { 6 | connectionName?: string; 7 | } 8 | 9 | export interface TypegooseModuleOptions { 10 | uri: string; 11 | [key: string]: any; 12 | } 13 | 14 | export interface TypegooseOptionsFactory { 15 | createTypegooseOptions(): 16 | | Promise 17 | | TypegooseModuleOptions; 18 | } 19 | 20 | export interface TypegooseModuleAsyncOptions extends Pick { 21 | connectionName?: string; 22 | useExisting?: Type; 23 | useClass?: Type; 24 | useFactory?: ( 25 | ...args: any[] 26 | ) => Promise | TypegooseModuleOptions; 27 | inject?: any[]; 28 | } 29 | -------------------------------------------------------------------------------- /sample/nest-ms-provider/src/users/users.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Post, Body, HttpException } from '@nestjs/common' 2 | import { UsersService } from './users.service' 3 | import { UserDto } from './users.dto' 4 | import { MessagePattern } from '@nestjs/microservices' 5 | import { of, Observable } from 'rxjs' 6 | import { delay } from 'rxjs/operators' 7 | 8 | @Controller() 9 | export class UsersController { 10 | constructor(private readonly usersService: UsersService) {} 11 | 12 | @Post('/register') 13 | async register(@Body() createUserDto: UserDto) { 14 | return await this.usersService.register(createUserDto) 15 | } 16 | 17 | @Get() 18 | async findAll() { 19 | return this.usersService.findAll() 20 | } 21 | 22 | @MessagePattern({ cmd: 'sum' }) 23 | accumulate(data: number[]): Observable { 24 | // if (1 > 0) { 25 | // throw new HttpException('error', 503) 26 | // } 27 | return of(15).pipe(delay(2000)) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /sample/hello-world/test/users/users-multi-db.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { request, genFixtures, prefix } from '../test-helper' 2 | import { UserTemplate, AccountTemplate } from '../model-mock-template' 3 | 4 | describe('AppController (e2e) multi db ', () => { 5 | let user = null 6 | let userId: string 7 | beforeAll(async function () { 8 | // 自动生成 User fixture 9 | const userData = await genFixtures(UserTemplate, 1, 'User') 10 | user = userData[0] 11 | userId = user._id.toString() 12 | 13 | // 自动生成 Account fixture,并且通过 fixData 函数来修改值 14 | await genFixtures(AccountTemplate, 1, 'Account', (it: any) => { 15 | it.userId = userId 16 | return it 17 | }) 18 | }) 19 | 20 | it('find account', async () => { 21 | console.log('userId', userId) 22 | const { body } = await request 23 | .get(prefix + '/users/account') 24 | .query({ userId: userId }) 25 | .expect(200) 26 | 27 | console.log('body', body) 28 | expect(body.data.userId).toEqual(userId) 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /sample/nest-keycloak/src/common/filters/http-exception.filter.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ExceptionFilter, 3 | ArgumentsHost, 4 | Catch, 5 | HttpException, 6 | HttpStatus, 7 | Logger 8 | } from '@nestjs/common' 9 | 10 | @Catch(HttpException) 11 | export class HttpExceptionFilter implements ExceptionFilter { 12 | catch(exception: HttpException, host: ArgumentsHost) { 13 | const ctx = host.switchToHttp() 14 | const response = ctx.getResponse() 15 | 16 | const message = exception.message 17 | Logger.log('系统内部错误', message) 18 | const errorResponse = { 19 | message: message, 20 | code: exception.getStatus() || 1 // 自定义code 21 | // url: request.originalUrl // 错误的url地址 22 | } 23 | const status = 24 | exception instanceof HttpException 25 | ? exception.getStatus() 26 | : HttpStatus.INTERNAL_SERVER_ERROR 27 | // 设置返回的状态码、请求头、发送错误信息 28 | response.status(status) 29 | response.header('Content-Type', 'application/json; charset=utf-8') 30 | response.send(errorResponse) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /sample/nest-zeebe/src/common/filters/http-exception.filter.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ExceptionFilter, 3 | ArgumentsHost, 4 | Catch, 5 | HttpException, 6 | HttpStatus, 7 | Logger 8 | } from '@nestjs/common' 9 | 10 | @Catch(HttpException) 11 | export class HttpExceptionFilter implements ExceptionFilter { 12 | catch(exception: HttpException, host: ArgumentsHost) { 13 | const ctx = host.switchToHttp() 14 | const response = ctx.getResponse() 15 | 16 | const message = exception.message 17 | Logger.log('系统内部错误', message) 18 | const errorResponse = { 19 | message: message, 20 | code: exception.getStatus() || 1 // 自定义code 21 | // url: request.originalUrl // 错误的url地址 22 | } 23 | const status = 24 | exception instanceof HttpException 25 | ? exception.getStatus() 26 | : HttpStatus.INTERNAL_SERVER_ERROR 27 | // 设置返回的状态码、请求头、发送错误信息 28 | response.status(status) 29 | response.header('Content-Type', 'application/json; charset=utf-8') 30 | response.send(errorResponse) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/mongoose/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@kalengo/mongoose", 3 | "version": "1.0.3", 4 | "description": "aka mongoose", 5 | "scripts": { 6 | "test": "npm run test", 7 | "build": "tsc", 8 | "test:e2e": "jest --config ./test/jest-e2e.json --detectOpenHandles" 9 | }, 10 | "publishConfig": { 11 | "access": "public" 12 | }, 13 | "author": "nick", 14 | "license": "MIT", 15 | "dependencies": { 16 | "@nestjs/common": "^10.3.2", 17 | "@typegoose/typegoose": "^12.7.0", 18 | "config": "^3.3.12", 19 | "is-class": "^0.0.9", 20 | "lodash": "^4.17.21", 21 | "mockjs": "^1.1.0", 22 | "reflect-metadata": "^0.1.13" 23 | }, 24 | "gitHead": "4b2499c98f7b2334e390a18ab220547f7e00ee29", 25 | "devDependencies": { 26 | "@nestjs/platform-express": "^10.3.2", 27 | "@nestjs/testing": "^10.3.2", 28 | "@types/jest": "^29.5.12", 29 | "@types/mockjs": "^1.0.10", 30 | "jest": "^29.7.0", 31 | "source-map-support": "^0.5.21", 32 | "supertest": "^7.0.0", 33 | "ts-jest": "^29.2.5", 34 | "typescript": "^5.6.2" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tools/gulp/tasks/move.ts: -------------------------------------------------------------------------------- 1 | import {task, src, dest, series} from 'gulp' 2 | import {getDirs} from '../util/task-helpers' 3 | import {samplePath} from '../config' 4 | import {join} from 'path' 5 | 6 | /** 7 | * Moves the hello-world project into the 8 | * `boilerplate/*` dirs. 9 | */ 10 | function moveBoilerplate () { 11 | return src(['sample/hello-world/**/*', 'sample/hello-world/**/.*', '!sample/hello-world/.idea', '!sample/hello-world/node_modules/**/*', '!sample/hello-world/dist/**/*']) 12 | .pipe(dest('template/boilerplate/')) 13 | } 14 | 15 | /** 16 | * Moves the compiled aka files into the 17 | * `samples/*` and `integration/*` dirs. 18 | */ 19 | function move () { 20 | const samplesDirs = getDirs(samplePath) 21 | 2 22 | const distFiles = src(['node_modules/@kalengo/**/*']) 23 | 24 | return samplesDirs.reduce( 25 | (distFile, dir) => distFile.pipe(dest(join(dir, '/node_modules/@kalengo'))), 26 | distFiles 27 | ) 28 | } 29 | 30 | task('move:boilerplate', moveBoilerplate) 31 | task('move:template', series('copy-misc', 'clean:template', 'move:boilerplate')) 32 | task('move', move) 33 | -------------------------------------------------------------------------------- /packages/utils/src/LoggerConfig.ts: -------------------------------------------------------------------------------- 1 | import * as config from 'config' 2 | 3 | const defaultLogConfig: any = { 4 | level: 'info', 5 | dateformat: 'yyyy-mm-dd HH:MM:ss.L', 6 | inspectOpt: { 7 | showHidden: false, // if true then the object's non-enumerable properties will be shown too. Defaults to false 8 | depth: 5 // tells inspect how many times to recurse while formatting the object. This is useful for inspecting large complicated objects. Defaults to 2. To make it recurse indefinitely pass null. 9 | } 10 | } 11 | 12 | if (config.has('log')) { 13 | Object.assign(defaultLogConfig, config.get('log')) 14 | } 15 | 16 | // 一行显示 Object 17 | if (config.has('log.one_line_object') && config.get('log.one_line_object')) { 18 | Object.assign(defaultLogConfig, { 19 | preprocess: function (data) { 20 | const l = data.args.length 21 | for (let i = 0; i < l; i++) { 22 | if (typeof data.args[i] === 'object') { 23 | data.args[i] = JSON.stringify(data.args[i], null, 0) 24 | } 25 | } 26 | } 27 | }) 28 | } 29 | 30 | console.log('aksjs util logger : defaultLogConfig', defaultLogConfig) 31 | 32 | export { defaultLogConfig } 33 | -------------------------------------------------------------------------------- /sample/hello-world/test/test-helper.ts: -------------------------------------------------------------------------------- 1 | import { TestDatabaseHelper } from '@kalengo/mongoose' 2 | import { INestApplication } from '@nestjs/common' 3 | import * as supertest from 'supertest' 4 | import { bootstrap, prefix as pre } from './main' 5 | import { TestingModule } from '@nestjs/testing' 6 | 7 | process.env.NODE_ENV = 'test' 8 | console.log('current env', process.env.NODE_ENV) 9 | 10 | let app: INestApplication 11 | let testModule: TestingModule 12 | let request: supertest.SuperTest 13 | const prefix = '/' + pre 14 | 15 | export async function genFixtures( 16 | template: object, 17 | nums: number, 18 | modelName: string, 19 | fixData?: (it: object, i: number) => any 20 | ) { 21 | return TestDatabaseHelper.genFixtures( 22 | app as any, 23 | template, 24 | nums, 25 | modelName, 26 | fixData 27 | ) 28 | } 29 | 30 | beforeAll(async function () { 31 | const res = await bootstrap() 32 | app = res.app 33 | request = res.request 34 | testModule = res.testModule 35 | await TestDatabaseHelper.clearDatabase(app as any) 36 | }) 37 | 38 | afterAll(async () => { 39 | await app.close() 40 | }) 41 | export { app, request, testModule, prefix } 42 | -------------------------------------------------------------------------------- /sample/redis-redlock-cache/src/users/users.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Post } from '@nestjs/common' 2 | import { UsersService } from './users.service' 3 | 4 | @Controller('users') 5 | export class UsersController { 6 | constructor(private readonly usersService: UsersService) {} 7 | 8 | @Post('/getAndSet') 9 | async getAndSet() { 10 | return await this.usersService.getAndSet() 11 | } 12 | 13 | @Get('/mutex') 14 | async mutex() { 15 | return await this.usersService.mutex() 16 | } 17 | 18 | @Get('/buffer') 19 | async buffer() { 20 | return await this.usersService.buffer() 21 | } 22 | 23 | @Get() 24 | async findAll() { 25 | return this.usersService.findAll() 26 | } 27 | 28 | @Get('/hello') 29 | async hello(): Promise { 30 | return 'Hello World!' 31 | } 32 | 33 | @Get('/decorator/mutex') 34 | async decoratorMutex() { 35 | return await this.usersService.decoratorMutex({ 36 | list: ['list1'], 37 | name: 'deo' 38 | }) 39 | } 40 | 41 | @Get('/decorator/buffer') 42 | async decoratorBuffer() { 43 | return await this.usersService.decoratorBuffer({ 44 | list: ['list1'], 45 | name: 'deo' 46 | }) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/mongoose/src/config.parse.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash' 2 | import * as config from 'config' 3 | 4 | export type MongoConfigs = { 5 | url: string 6 | name: string 7 | options?: { 8 | useNewUrlParser: boolean 9 | useUnifiedTopology: boolean 10 | } 11 | } 12 | 13 | export function parseConfig(): { 14 | mongoConfigs: MongoConfigs[] 15 | debugMongoose: boolean 16 | } { 17 | let mongoConfigs: MongoConfigs[] 18 | try { 19 | // 兼容旧结构 20 | if (config.has('database.mongodb')) { 21 | mongoConfigs = config.get('database.mongodb') 22 | } else { 23 | mongoConfigs = config.get('mongodb.connections') 24 | } 25 | } catch (e) { 26 | throw new Error('mongodb config 不能为空') 27 | } 28 | if (_.isEmpty(mongoConfigs)) throw new Error('mongodb config 不能为空') 29 | let debugMongoose = false 30 | try { 31 | // 兼容旧结构 32 | if (config.has('database.mongoDebug')) { 33 | debugMongoose = config.get('database.mongoDebug') 34 | } 35 | if (config.has('mongodb.debug')) { 36 | debugMongoose = config.get('mongodb.debug') 37 | } 38 | } catch (e) { 39 | throw new Error('mongodb debug config 不正确') 40 | } 41 | return { mongoConfigs, debugMongoose } 42 | } 43 | -------------------------------------------------------------------------------- /packages/web/src/http.exception.filter.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ExceptionFilter, 3 | ArgumentsHost, 4 | Catch, 5 | HttpException, 6 | HttpStatus 7 | } from '@nestjs/common' 8 | import { logger } from '@kalengo/utils' 9 | import { BusinessException } from './exceptions/business.exception' 10 | 11 | @Catch() 12 | export class HttpExceptionFilter implements ExceptionFilter { 13 | catch(exception: HttpException, host: ArgumentsHost) { 14 | const ctx = host.switchToHttp() 15 | const response = ctx.getResponse() 16 | const request = ctx.getRequest() 17 | 18 | const message = exception.message 19 | logger.info('系统内部错误', message) 20 | 21 | const status = 22 | exception instanceof HttpException 23 | ? exception.getStatus() 24 | : HttpStatus.INTERNAL_SERVER_ERROR 25 | 26 | const code = 27 | exception instanceof BusinessException ? exception.getCode() : status 28 | 29 | const errorResponse = { 30 | message: message, 31 | code: code, // 自定义code 32 | url: request.originalUrl // 错误的url地址 33 | } 34 | response.status(status) 35 | response.header('Content-Type', 'application/json; charset=utf-8') 36 | response.send(errorResponse) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/mongoose/src/test.database.helper.ts: -------------------------------------------------------------------------------- 1 | import { Logger, Module } from '@nestjs/common' 2 | import { getConnectionToken, getModelToken } from './typegoose' 3 | import * as Mock from 'mockjs' 4 | import { parseConfig } from './config.parse' 5 | 6 | @Module({}) 7 | export class TestDatabaseHelper { 8 | static async genFixtures( 9 | app, 10 | template: object, 11 | nums: number, 12 | modelName: string, 13 | fixData?: (it: object, i: number) => any 14 | ) { 15 | if (!fixData) fixData = (it: object, index: number) => it 16 | const model = app.get(getModelToken(modelName)) 17 | const items = Array(nums) 18 | .fill(0) 19 | .map((it: object) => Mock.mock(template)) 20 | .map(fixData) 21 | Logger.debug('initFixtures ', items.length + '') 22 | await model.insertMany(items) 23 | return items 24 | } 25 | 26 | static async clearDatabase(app) { 27 | const { mongoConfigs } = parseConfig() 28 | for (const c of mongoConfigs) { 29 | const connect = app.get(getConnectionToken(c.name)) 30 | for (const key of Object.keys(connect.collections)) { 31 | Logger.debug('delete ', key + '') 32 | await connect.collections[key].deleteMany({}) 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "klg-nest-starter", 3 | "version": "1.1.4", 4 | "description": "klg-init 的模板", 5 | "main": "index.js", 6 | "directories": { 7 | "doc": "docs", 8 | "example": "examples" 9 | }, 10 | "scripts": { 11 | "clean": "rimraf temp boilerplate/.idea boilerplate/node_modules", 12 | "build": "npm run clean && npx klg-init temp --force --silent --template=./", 13 | "pretest": "npm run build && npm run install-deps", 14 | "test": "cd temp && npm run test && npm run test:e2e", 15 | "install-deps": "cd temp && npm i --registry=https://registry.npm.taobao.org" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git@gitee.com:aikaola/klg-nest-starter.git" 20 | }, 21 | "author": "Nick", 22 | "license": "MIT", 23 | "devDependencies": { 24 | "@types/node": "^13.11.1", 25 | "delete-empty": "^3.0.0", 26 | "gulp": "^4.0.2", 27 | "gulp-clean": "^0.4.0", 28 | "gulp-rename": "^2.0.0", 29 | "gulp-sourcemaps": "^2.6.5", 30 | "gulp-typescript": "^6.0.0-alpha.1", 31 | "ts-node": "^8.8.2", 32 | "typescript": "^3.8.3", 33 | "vuepress": "^1.4.1" 34 | }, 35 | "dependencies": { 36 | "klg-init": "^1.1.1", 37 | "lerna": "^3.20.2" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /sample/hello-world/src/users/users.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | import { User, UserModel, IUserModel } from './model/user.model' 3 | import { InjectModel } from 'nestjs-typegoose' 4 | import { AccountModel, Account, IAccountModel } from './model/account.model' 5 | import { UserDto } from './users.dto' 6 | 7 | @Injectable() 8 | export class UsersService { 9 | constructor( 10 | @InjectModel(User) private readonly userModel: UserModel, 11 | @InjectModel(Account) private readonly accountModel: AccountModel 12 | ) {} 13 | 14 | async register(createUsersDto: UserDto): Promise { 15 | const createdUser = new this.userModel(createUsersDto) 16 | const user = await createdUser.save() 17 | await user.registerSuccess() 18 | return user 19 | } 20 | 21 | async findAll(): Promise { 22 | return this.userModel.find() 23 | } 24 | 25 | async getAccountAndUser(userId: string): Promise { 26 | const user = await this.userModel.findById(userId) 27 | if (!user) throw new Error('User not found ' + userId) 28 | const account = await this.accountModel.findOne({ userId: user.id }) 29 | if (!account) throw new Error('Account not found ' + user.id) 30 | return account 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /sample/nest-keycloak/test/main.ts: -------------------------------------------------------------------------------- 1 | import * as supertest from 'supertest' 2 | import { ApplicationModule } from '../src/app.module' 3 | import { Test } from '@nestjs/testing' 4 | import { appSettings } from '../src/settings' 5 | import { AuthGuard, RolesGuard } from '@kalengo/keycloak' 6 | 7 | export { prefix } from '../src/settings' 8 | 9 | export async function bootstrap() { 10 | // init nestjs 11 | const testModule = await Test.createTestingModule({ 12 | imports: [ApplicationModule] 13 | }) 14 | .overrideGuard(AuthGuard) 15 | .useValue({ canActivate: () => true }) 16 | .overrideGuard(RolesGuard) 17 | .useValue({ canActivate: () => true }) 18 | .compile() 19 | const app = testModule.createNestApplication() 20 | appSettings(app) 21 | await app.init() 22 | const request = supertest(app.getHttpServer()) 23 | return { app, request, testModule } 24 | } 25 | 26 | export async function bootstrapWithAuth() { 27 | // init nestjs 28 | const testModule = await Test.createTestingModule({ 29 | imports: [ApplicationModule] 30 | }).compile() 31 | const app = testModule.createNestApplication() 32 | appSettings(app) 33 | await app.init() 34 | const request = supertest(app.getHttpServer()) 35 | return { app, request, testModule } 36 | } 37 | -------------------------------------------------------------------------------- /docs/extend/event.md: -------------------------------------------------------------------------------- 1 | 事件处理是我们开发中常用的模块,不过 Nest 没有官方支持, 2 | 这里推荐大家使用 [nest-event](https://github.com/javascript-dragons/nest-event) 这个开源项目. 3 | 4 | 该项目的特点: 5 | 6 | 1. 抛出事件 7 | ```ts 8 | import { NestEventEmitter } from './nest-event'; 9 | 10 | @Controller('user') 11 | export class UserController { 12 | constructor( 13 | private readonly nestEventEmitter: NestEventEmitter, 14 | ) {} 15 | 16 | @Post('signup') 17 | signup() { 18 | // ... 19 | this.nestEventEmitter.emit('user-created', user); 20 | } 21 | } 22 | ``` 23 | 通过注入获得 eventEmitter 24 | 25 | 2. 响应事件 26 | ```ts 27 | import { Injectable } from '@nestjs/common'; 28 | import { On } from './nest-event'; 29 | import { User } from './interfaces'; 30 | 31 | @Injectable() 32 | export class EmailService { 33 | 34 | @On('user-created') 35 | onUserCreated(user: User){ 36 | // send verification email 37 | } 38 | } 39 | ``` 40 | 使用注解来响应事件,简洁明了 41 | 42 | 3. 还支持给 event 加上类型 43 | 44 | ```ts 45 | // define your events 46 | interface Events { 47 | request: (request: Request, response: Response) => void; 48 | done: void; 49 | } 50 | this.nestEventEmitter.strictEmitter().emit('done'); 51 | ``` 52 | 53 | 如果 emit 的参数不符合类型定义,将会报错, 具体见 [StrictEventEmitter](https://github.com/bterlson/strict-event-emitter-types) 54 | -------------------------------------------------------------------------------- /sample/hello-world/src/users/users.service.spec.ts: -------------------------------------------------------------------------------- 1 | import * as mongoose from 'mongoose' 2 | import { getModelToken } from 'nestjs-typegoose' 3 | import { Test } from '@nestjs/testing' 4 | import { UsersController } from './users.controller' 5 | import { UsersService } from './users.service' 6 | 7 | mongoose.set('debug', true) 8 | 9 | describe('UsersService', () => { 10 | let usersService: UsersService 11 | 12 | beforeEach(async () => { 13 | const module = await Test.createTestingModule({ 14 | controllers: [UsersController], 15 | providers: [ 16 | UsersService, 17 | { 18 | provide: getModelToken('User'), 19 | useValue: { 20 | find: () => ({ a: 1 }) 21 | } 22 | }, 23 | { 24 | provide: getModelToken('Account'), 25 | useValue: {} 26 | } 27 | ] 28 | }).compile() 29 | usersService = module.get(UsersService) 30 | }) 31 | 32 | describe('findAll', () => { 33 | it('should return an array of users', async () => { 34 | const result = { a: 1 } 35 | // jest.spyOn(usersService, 'findAll').mockImplementation(async () => result) 36 | const res = await usersService.findAll() 37 | expect(res).toEqual(result) 38 | }) 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /tools/gulp/tasks/clean.ts: -------------------------------------------------------------------------------- 1 | import {task, src, series} from 'gulp' 2 | import * as clean from 'gulp-clean' 3 | import * as deleteEmpty from 'delete-empty' 4 | import {source} from '../config' 5 | 6 | 7 | /** 8 | * Cleans the build output assets from the packages folders 9 | */ 10 | function cleanOutput () { 11 | return src( 12 | [ 13 | `${ source }/**/*.js`, 14 | `${ source }/**/*.d.ts`, 15 | `${ source }/**/*.js.map`, 16 | `${ source }/**/*.d.ts.map`, 17 | `${ source }/**/node_modules/`, 18 | `${ source }/**/package-lock.json`, 19 | `sample/**/package-lock.json`, 20 | ], 21 | { 22 | read: false 23 | } 24 | ).pipe(clean()) 25 | } 26 | 27 | /** 28 | * Cleans empty dirs 29 | */ 30 | function cleanDirs (done: () => void) { 31 | deleteEmpty.sync(`${ source }/`) 32 | done() 33 | } 34 | 35 | /** 36 | * Cleans the build output assets from the packages folders 37 | */ 38 | function cleanTemplate () { 39 | // return del(`${ source }/**/*`, {force: true}) 40 | return src('template/boilerplate', {read: false}) 41 | .pipe(clean({force: true, allowEmpty: true})) 42 | } 43 | 44 | task('clean:output', cleanOutput) 45 | task('clean:dirs', cleanDirs) 46 | task('clean:bundle', series('clean:output', 'clean:dirs')) 47 | task('clean:template', cleanTemplate) 48 | -------------------------------------------------------------------------------- /sample/nest-ms-consumer/test/users/users-register.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { request, testModule, prefix } from '../test-helper' 2 | import { UsersService } from '../../src/users/users.service' 3 | 4 | describe('AppController (e2e) with db ', () => { 5 | it('register with service mock', async () => { 6 | const usersService = testModule.get(UsersService) 7 | const spy = jest 8 | .spyOn(usersService, 'register') 9 | .mockImplementation(async () => ({ mock: true } as any)) 10 | 11 | console.log('prefix', prefix) 12 | const { body } = await request 13 | .post(prefix + '/register') 14 | .send({ name: 'nick', phone: '12345' }) 15 | .expect(201) 16 | .expect({ 17 | code: 0, 18 | message: 'success', 19 | data: { 20 | mock: true 21 | } 22 | }) 23 | console.log('body', body) 24 | expect(spy).toHaveBeenCalled() 25 | // 一定要 restore 不然会影响其他测试 26 | spy.mockRestore() 27 | }) 28 | 29 | it('register', async () => { 30 | const { body } = await request 31 | .post(prefix + '/register') 32 | .send({ name: 'nick', phone: '12345' }) 33 | .expect(201) 34 | expect(body.code).toEqual(0) 35 | expect(body.message).toEqual('success') 36 | expect(body.data.name).toEqual('nick') 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /sample/nest-ms-consumer/src/users/users.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Post, Body, Inject } from '@nestjs/common' 2 | import { UsersService } from './users.service' 3 | import { UserDto } from './users.dto' 4 | import { Observable } from 'rxjs' 5 | import { ClientProxy } from '@nestjs/microservices' 6 | import { logger } from '@kalengo/utils' 7 | 8 | @Controller('/') 9 | export class UsersController { 10 | constructor( 11 | private readonly usersService: UsersService, 12 | @Inject('MATH_SERVICE') private readonly client: ClientProxy 13 | ) {} 14 | 15 | @Post('/register') 16 | async register(@Body() createUserDto: UserDto) { 17 | return await this.usersService.register(createUserDto) 18 | } 19 | 20 | @Get() 21 | async findAll() { 22 | return this.usersService.findAll() 23 | } 24 | 25 | @Get('/accumulate') 26 | async accumulate(): Promise { 27 | const pattern = { cmd: 'sum' } 28 | const data = [1, 2, 3, 4, 5] 29 | const res = this.client.send(pattern, data) 30 | const total = await res.toPromise() 31 | logger.info('total', total) 32 | return res.toPromise() 33 | } 34 | 35 | @Get('/accumulate2') 36 | accumulate2(): Observable { 37 | const pattern = { cmd: 'sum' } 38 | const data = [1, 2, 3, 4, 5] 39 | return this.client.send(pattern, data) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /sample/nest-ms-provider/test/users/users-register.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { request, testModule, prefix } from '../test-helper' 2 | import { UsersService } from '../../src/users/users.service' 3 | 4 | describe('AppController (e2e) with db ', () => { 5 | it('register with service mock', async () => { 6 | const usersService = testModule.get(UsersService) 7 | const spy = jest 8 | .spyOn(usersService, 'register') 9 | .mockImplementation(async () => ({ mock: true } as any)) 10 | 11 | console.log('prefix', prefix) 12 | const { body } = await request 13 | .post(prefix + '/register') 14 | .send({ name: 'nick', phone: '12345' }) 15 | .expect(201) 16 | .expect({ 17 | code: 0, 18 | message: 'success', 19 | data: { 20 | mock: true 21 | } 22 | }) 23 | console.log('body', body) 24 | expect(spy).toHaveBeenCalled() 25 | // 一定要 restore 不然会影响其他测试 26 | spy.mockRestore() 27 | }) 28 | 29 | it('register', async () => { 30 | const { body } = await request 31 | .post(prefix + '/register') 32 | .send({ name: 'nick', phone: '12345' }) 33 | .expect(201) 34 | expect(body.code).toEqual(0) 35 | expect(body.message).toEqual('success') 36 | expect(body.data.name).toEqual('nick') 37 | }) 38 | 39 | it('find all', () => { 40 | request.get(prefix + '/').expect(200) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /sample/hello-world/src/users/users.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { getModelToken } from 'nestjs-typegoose' 2 | import { Test } from '@nestjs/testing' 3 | import { UsersController } from './users.controller' 4 | import { UsersService } from './users.service' 5 | 6 | describe('UsersController', () => { 7 | let usersController: UsersController 8 | let usersService: UsersService 9 | 10 | beforeEach(async () => { 11 | const module = await Test.createTestingModule({ 12 | controllers: [UsersController], 13 | providers: [ 14 | UsersService, 15 | { 16 | provide: getModelToken('User'), 17 | useValue: {} 18 | }, 19 | { 20 | provide: getModelToken('Account'), 21 | useValue: {} 22 | } 23 | ] 24 | }).compile() 25 | 26 | usersService = module.get(UsersService) 27 | usersController = module.get(UsersController) 28 | }) 29 | 30 | describe('register', () => { 31 | it('register a user success', async () => { 32 | const userDto = { name: 'miao', phone: '13646876567' } 33 | const mockCallback = jest.fn() 34 | jest.spyOn(usersService, 'register').mockImplementation(mockCallback) 35 | 36 | await usersController.register(userDto) 37 | expect(mockCallback).toHaveBeenCalledTimes(1) 38 | expect(mockCallback).toBeCalledWith(userDto) 39 | }) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /sample/nest-simple/test/users/users-register.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { request, testModule, prefix } from '../test-helper' 2 | import { UsersService } from '../../src/users/users.service' 3 | 4 | describe('AppController (e2e) with db ', () => { 5 | it('register with service mock', async () => { 6 | const usersService = testModule.get(UsersService) 7 | const spy = jest 8 | .spyOn(usersService, 'register') 9 | .mockImplementation(async () => ({ mock: true } as any)) 10 | 11 | console.log('prefix', prefix) 12 | const { body } = await request 13 | .post(prefix + '/users/register') 14 | .send({ name: 'nick', phone: '12345' }) 15 | .expect(201) 16 | .expect({ 17 | code: 0, 18 | message: 'success', 19 | data: { 20 | mock: true 21 | } 22 | }) 23 | console.log('body', body) 24 | expect(spy).toHaveBeenCalled() 25 | // 一定要 restore 不然会影响其他测试 26 | spy.mockRestore() 27 | }) 28 | 29 | it('register', async () => { 30 | const { body } = await request 31 | .post(prefix + '/users/register') 32 | .send({ name: 'nick', phone: '12345' }) 33 | .expect(201) 34 | expect(body.code).toEqual(0) 35 | expect(body.message).toEqual('success') 36 | expect(body.data.name).toEqual('nick') 37 | }) 38 | 39 | it('find all', () => { 40 | request.get(prefix + '/users').expect(200) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /sample/nest-keycloak/test/users/users-auth.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { bootstrapWithAuth } from '../main' 2 | import * as supertest from 'supertest' 3 | 4 | describe('users-auth.e2e-spec.ts', () => { 5 | let request: supertest.SuperTest 6 | 7 | beforeAll(async function () { 8 | const res = await bootstrapWithAuth() 9 | request = res.request 10 | }) 11 | 12 | it('not login get 401 ', () => { 13 | return request.get('/users/hello').expect(401) 14 | }) 15 | 16 | let token: string 17 | it('login get token', async () => { 18 | const { body } = await request.get('/users/login').expect(200) 19 | expect(body.data.token).not.toBeUndefined() 20 | token = body.data.token 21 | console.log('token', token) 22 | }) 23 | 24 | it('hello is public for all user', () => { 25 | return request 26 | .get('/users/hello') 27 | .set({ Authorization: `Bearer ${token}` }) 28 | .expect(200) 29 | .expect({ 30 | code: 0, 31 | data: 'Hello World!', 32 | message: 'success' 33 | }) 34 | }) 35 | 36 | it('get user info is only for user role', () => { 37 | return request 38 | .get('/users/info') 39 | .set({ Authorization: `Bearer ${token}` }) 40 | .expect(200) 41 | }) 42 | 43 | it('get all user is not allowed', () => { 44 | return request 45 | .get('/users/') 46 | .set({ Authorization: `Bearer ${token}` }) 47 | .expect(406) 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /common/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12-alpine as base 2 | 3 | WORKDIR /app 4 | COPY package.json . 5 | RUN echo http://mirrors.aliyun.com/alpine/v3.6/main/ > /etc/apk/repositories && \ 6 | echo http://mirrors.aliyun.com/alpine/v3.6/community/ >> /etc/apk/repositories && \ 7 | apk update 8 | RUN npm install --production --registry=https://registry.npm.taobao.org --disturl=https://npm.taobao.org/mirrors/node --build-from-source && mv node_modules prod_node_modules 9 | RUN npm install --registry=https://registry.npm.taobao.org --disturl=https://npm.taobao.org/mirrors/node --build-from-source 10 | 11 | FROM node:12-alpine as build 12 | WORKDIR /app 13 | COPY --from=base /app/node_modules ./node_modules 14 | COPY src ./src 15 | COPY tsconfig.json ./ 16 | COPY tsconfig.build.json ./ 17 | COPY config ./config 18 | COPY package.json ./ 19 | RUN npm run build 20 | 21 | FROM node:12-alpine as prod 22 | RUN echo http://mirrors.aliyun.com/alpine/v3.6/main/ > /etc/apk/repositories && \ 23 | echo http://mirrors.aliyun.com/alpine/v3.6/community/ >> /etc/apk/repositories && \ 24 | apk update && apk add ca-certificates && \ 25 | apk add -U tzdata && \ 26 | cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ 27 | echo "Asia/Shanghai" > /etc/timezone 28 | ENV TZ=Asia/Shanghai 29 | 30 | WORKDIR /app 31 | COPY --from=base /app/prod_node_modules ./node_modules 32 | COPY --from=build /app/dist ./dist 33 | COPY --from=build /app/config ./config 34 | COPY --from=build /app/package.json . 35 | 36 | CMD ["npm","start"] 37 | EXPOSE 3000 38 | -------------------------------------------------------------------------------- /sample/hello-world/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12-alpine as base 2 | 3 | WORKDIR /app 4 | COPY package.json . 5 | RUN echo http://mirrors.aliyun.com/alpine/v3.6/main/ > /etc/apk/repositories && \ 6 | echo http://mirrors.aliyun.com/alpine/v3.6/community/ >> /etc/apk/repositories && \ 7 | apk update 8 | RUN npm install --production --registry=https://registry.npm.taobao.org --disturl=https://npm.taobao.org/mirrors/node --build-from-source && mv node_modules prod_node_modules 9 | RUN npm install --registry=https://registry.npm.taobao.org --disturl=https://npm.taobao.org/mirrors/node --build-from-source 10 | 11 | FROM node:12-alpine as build 12 | WORKDIR /app 13 | COPY --from=base /app/node_modules ./node_modules 14 | COPY src ./src 15 | COPY tsconfig.json ./ 16 | COPY tsconfig.build.json ./ 17 | COPY config ./config 18 | COPY package.json ./ 19 | RUN npm run build 20 | 21 | FROM node:12-alpine as prod 22 | RUN echo http://mirrors.aliyun.com/alpine/v3.6/main/ > /etc/apk/repositories && \ 23 | echo http://mirrors.aliyun.com/alpine/v3.6/community/ >> /etc/apk/repositories && \ 24 | apk update && apk add ca-certificates && \ 25 | apk add -U tzdata && \ 26 | cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ 27 | echo "Asia/Shanghai" > /etc/timezone 28 | ENV TZ=Asia/Shanghai 29 | 30 | WORKDIR /app 31 | COPY --from=base /app/prod_node_modules ./node_modules 32 | COPY --from=build /app/dist ./dist 33 | COPY --from=build /app/config ./config 34 | COPY --from=build /app/package.json . 35 | 36 | CMD ["npm","start"] 37 | EXPOSE 3000 38 | -------------------------------------------------------------------------------- /sample/nest-keycloak/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12-alpine as base 2 | 3 | WORKDIR /app 4 | COPY package.json . 5 | RUN echo http://mirrors.aliyun.com/alpine/v3.6/main/ > /etc/apk/repositories && \ 6 | echo http://mirrors.aliyun.com/alpine/v3.6/community/ >> /etc/apk/repositories && \ 7 | apk update 8 | RUN npm install --production --registry=https://registry.npm.taobao.org --disturl=https://npm.taobao.org/mirrors/node --build-from-source && mv node_modules prod_node_modules 9 | RUN npm install --registry=https://registry.npm.taobao.org --disturl=https://npm.taobao.org/mirrors/node --build-from-source 10 | 11 | FROM node:12-alpine as build 12 | WORKDIR /app 13 | COPY --from=base /app/node_modules ./node_modules 14 | COPY src ./src 15 | COPY tsconfig.json ./ 16 | COPY tsconfig.build.json ./ 17 | COPY config ./config 18 | COPY package.json ./ 19 | RUN npm run build 20 | 21 | FROM node:12-alpine as prod 22 | RUN echo http://mirrors.aliyun.com/alpine/v3.6/main/ > /etc/apk/repositories && \ 23 | echo http://mirrors.aliyun.com/alpine/v3.6/community/ >> /etc/apk/repositories && \ 24 | apk update && apk add ca-certificates && \ 25 | apk add -U tzdata && \ 26 | cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ 27 | echo "Asia/Shanghai" > /etc/timezone 28 | ENV TZ=Asia/Shanghai 29 | 30 | WORKDIR /app 31 | COPY --from=base /app/prod_node_modules ./node_modules 32 | COPY --from=build /app/dist ./dist 33 | COPY --from=build /app/config ./config 34 | COPY --from=build /app/package.json . 35 | 36 | CMD ["npm","start"] 37 | EXPOSE 3000 38 | -------------------------------------------------------------------------------- /sample/nest-simple/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12-alpine as base 2 | 3 | WORKDIR /app 4 | COPY package.json . 5 | RUN echo http://mirrors.aliyun.com/alpine/v3.6/main/ > /etc/apk/repositories && \ 6 | echo http://mirrors.aliyun.com/alpine/v3.6/community/ >> /etc/apk/repositories && \ 7 | apk update 8 | RUN npm install --production --registry=https://registry.npm.taobao.org --disturl=https://npm.taobao.org/mirrors/node --build-from-source && mv node_modules prod_node_modules 9 | RUN npm install --registry=https://registry.npm.taobao.org --disturl=https://npm.taobao.org/mirrors/node --build-from-source 10 | 11 | FROM node:12-alpine as build 12 | WORKDIR /app 13 | COPY --from=base /app/node_modules ./node_modules 14 | COPY src ./src 15 | COPY tsconfig.json ./ 16 | COPY tsconfig.build.json ./ 17 | COPY config ./config 18 | COPY package.json ./ 19 | RUN npm run build 20 | 21 | FROM node:12-alpine as prod 22 | RUN echo http://mirrors.aliyun.com/alpine/v3.6/main/ > /etc/apk/repositories && \ 23 | echo http://mirrors.aliyun.com/alpine/v3.6/community/ >> /etc/apk/repositories && \ 24 | apk update && apk add ca-certificates && \ 25 | apk add -U tzdata && \ 26 | cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ 27 | echo "Asia/Shanghai" > /etc/timezone 28 | ENV TZ=Asia/Shanghai 29 | 30 | WORKDIR /app 31 | COPY --from=base /app/prod_node_modules ./node_modules 32 | COPY --from=build /app/dist ./dist 33 | COPY --from=build /app/config ./config 34 | COPY --from=build /app/package.json . 35 | 36 | CMD ["npm","start"] 37 | EXPOSE 3000 38 | -------------------------------------------------------------------------------- /sample/nest-zeebe/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12-alpine as base 2 | 3 | WORKDIR /app 4 | COPY package.json . 5 | RUN echo http://mirrors.aliyun.com/alpine/v3.6/main/ > /etc/apk/repositories && \ 6 | echo http://mirrors.aliyun.com/alpine/v3.6/community/ >> /etc/apk/repositories && \ 7 | apk update 8 | RUN npm install --production --registry=https://registry.npm.taobao.org --disturl=https://npm.taobao.org/mirrors/node --build-from-source && mv node_modules prod_node_modules 9 | RUN npm install --registry=https://registry.npm.taobao.org --disturl=https://npm.taobao.org/mirrors/node --build-from-source 10 | 11 | FROM node:12-alpine as build 12 | WORKDIR /app 13 | COPY --from=base /app/node_modules ./node_modules 14 | COPY src ./src 15 | COPY tsconfig.json ./ 16 | COPY tsconfig.build.json ./ 17 | COPY config ./config 18 | COPY package.json ./ 19 | RUN npm run build 20 | 21 | FROM node:12-alpine as prod 22 | RUN echo http://mirrors.aliyun.com/alpine/v3.6/main/ > /etc/apk/repositories && \ 23 | echo http://mirrors.aliyun.com/alpine/v3.6/community/ >> /etc/apk/repositories && \ 24 | apk update && apk add ca-certificates && \ 25 | apk add -U tzdata && \ 26 | cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ 27 | echo "Asia/Shanghai" > /etc/timezone 28 | ENV TZ=Asia/Shanghai 29 | 30 | WORKDIR /app 31 | COPY --from=base /app/prod_node_modules ./node_modules 32 | COPY --from=build /app/dist ./dist 33 | COPY --from=build /app/config ./config 34 | COPY --from=build /app/package.json . 35 | 36 | CMD ["npm","start"] 37 | EXPOSE 3000 38 | -------------------------------------------------------------------------------- /sample/nest-ms-consumer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12-alpine as base 2 | 3 | WORKDIR /app 4 | COPY package.json . 5 | RUN echo http://mirrors.aliyun.com/alpine/v3.6/main/ > /etc/apk/repositories && \ 6 | echo http://mirrors.aliyun.com/alpine/v3.6/community/ >> /etc/apk/repositories && \ 7 | apk update 8 | RUN npm install --production --registry=https://registry.npm.taobao.org --disturl=https://npm.taobao.org/mirrors/node --build-from-source && mv node_modules prod_node_modules 9 | RUN npm install --registry=https://registry.npm.taobao.org --disturl=https://npm.taobao.org/mirrors/node --build-from-source 10 | 11 | FROM node:12-alpine as build 12 | WORKDIR /app 13 | COPY --from=base /app/node_modules ./node_modules 14 | COPY src ./src 15 | COPY tsconfig.json ./ 16 | COPY tsconfig.build.json ./ 17 | COPY config ./config 18 | COPY package.json ./ 19 | RUN npm run build 20 | 21 | FROM node:12-alpine as prod 22 | RUN echo http://mirrors.aliyun.com/alpine/v3.6/main/ > /etc/apk/repositories && \ 23 | echo http://mirrors.aliyun.com/alpine/v3.6/community/ >> /etc/apk/repositories && \ 24 | apk update && apk add ca-certificates && \ 25 | apk add -U tzdata && \ 26 | cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ 27 | echo "Asia/Shanghai" > /etc/timezone 28 | ENV TZ=Asia/Shanghai 29 | 30 | WORKDIR /app 31 | COPY --from=base /app/prod_node_modules ./node_modules 32 | COPY --from=build /app/dist ./dist 33 | COPY --from=build /app/config ./config 34 | COPY --from=build /app/package.json . 35 | 36 | CMD ["npm","start"] 37 | EXPOSE 3000 38 | -------------------------------------------------------------------------------- /sample/nest-ms-provider/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12-alpine as base 2 | 3 | WORKDIR /app 4 | COPY package.json . 5 | RUN echo http://mirrors.aliyun.com/alpine/v3.6/main/ > /etc/apk/repositories && \ 6 | echo http://mirrors.aliyun.com/alpine/v3.6/community/ >> /etc/apk/repositories && \ 7 | apk update 8 | RUN npm install --production --registry=https://registry.npm.taobao.org --disturl=https://npm.taobao.org/mirrors/node --build-from-source && mv node_modules prod_node_modules 9 | RUN npm install --registry=https://registry.npm.taobao.org --disturl=https://npm.taobao.org/mirrors/node --build-from-source 10 | 11 | FROM node:12-alpine as build 12 | WORKDIR /app 13 | COPY --from=base /app/node_modules ./node_modules 14 | COPY src ./src 15 | COPY tsconfig.json ./ 16 | COPY tsconfig.build.json ./ 17 | COPY config ./config 18 | COPY package.json ./ 19 | RUN npm run build 20 | 21 | FROM node:12-alpine as prod 22 | RUN echo http://mirrors.aliyun.com/alpine/v3.6/main/ > /etc/apk/repositories && \ 23 | echo http://mirrors.aliyun.com/alpine/v3.6/community/ >> /etc/apk/repositories && \ 24 | apk update && apk add ca-certificates && \ 25 | apk add -U tzdata && \ 26 | cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ 27 | echo "Asia/Shanghai" > /etc/timezone 28 | ENV TZ=Asia/Shanghai 29 | 30 | WORKDIR /app 31 | COPY --from=base /app/prod_node_modules ./node_modules 32 | COPY --from=build /app/dist ./dist 33 | COPY --from=build /app/config ./config 34 | COPY --from=build /app/package.json . 35 | 36 | CMD ["npm","start"] 37 | EXPOSE 3000 38 | -------------------------------------------------------------------------------- /sample/redis-redlock-cache/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12-alpine as base 2 | 3 | WORKDIR /app 4 | COPY package.json . 5 | RUN echo http://mirrors.aliyun.com/alpine/v3.6/main/ > /etc/apk/repositories && \ 6 | echo http://mirrors.aliyun.com/alpine/v3.6/community/ >> /etc/apk/repositories && \ 7 | apk update 8 | RUN npm install --production --registry=https://registry.npm.taobao.org --disturl=https://npm.taobao.org/mirrors/node --build-from-source && mv node_modules prod_node_modules 9 | RUN npm install --registry=https://registry.npm.taobao.org --disturl=https://npm.taobao.org/mirrors/node --build-from-source 10 | 11 | FROM node:12-alpine as build 12 | WORKDIR /app 13 | COPY --from=base /app/node_modules ./node_modules 14 | COPY src ./src 15 | COPY tsconfig.json ./ 16 | COPY tsconfig.build.json ./ 17 | COPY config ./config 18 | COPY package.json ./ 19 | RUN npm run build 20 | 21 | FROM node:12-alpine as prod 22 | RUN echo http://mirrors.aliyun.com/alpine/v3.6/main/ > /etc/apk/repositories && \ 23 | echo http://mirrors.aliyun.com/alpine/v3.6/community/ >> /etc/apk/repositories && \ 24 | apk update && apk add ca-certificates && \ 25 | apk add -U tzdata && \ 26 | cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ 27 | echo "Asia/Shanghai" > /etc/timezone 28 | ENV TZ=Asia/Shanghai 29 | 30 | WORKDIR /app 31 | COPY --from=base /app/prod_node_modules ./node_modules 32 | COPY --from=build /app/dist ./dist 33 | COPY --from=build /app/config ./config 34 | COPY --from=build /app/package.json . 35 | 36 | CMD ["npm","start"] 37 | EXPOSE 3000 38 | -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: 'Kalengo Nest', 3 | base: '/nest_doc/', 4 | description: 'Just playing around', 5 | themeConfig: { 6 | // displayAllHeaders: true, 7 | nav: [ 8 | { text: '首页', link: '/' }, 9 | { text: '快速开始', link: '/quickstart' }, 10 | { text: 'Github', link: 'https://github.com/kaolalicai/klg-nest-starter' } 11 | ], 12 | sidebar: [ 13 | { 14 | title: '从 Web 开始', // 必要的 15 | path: '/quickstart', // 可选的, 标题的跳转链接,应为绝对路径且必须存在 16 | collapsable: false, // 可选的, 默认值是 true, 17 | sidebarDepth: 1, // 可选的, 默认值是 1 18 | children: [ 19 | ['/quickstart', '快速开始'], 20 | ['/web', 'Web生命周期'], 21 | ['/test', '测试'] 22 | ] 23 | }, 24 | ['/database/mongoose', 'Database'], 25 | ['/config', '应用配置'], 26 | { 27 | title: '其他技术', 28 | path: '/extend/redis', 29 | collapsable: false, 30 | children: [ 31 | ['/extend/redis', 'Redis'], 32 | ['/extend/rabbitmq', 'RabbitMQ'], 33 | ['/extend/keycloak', 'Keycloak SSO 认证和授权'], 34 | ['/extend/logger', '日志 Logger'], 35 | ['/extend/event', '事件处理 Event'], 36 | ['/extend/crud', 'CRUD'], 37 | ['/extend/ms', '微服务调用'], 38 | ['/extend/workflow', '工作流/微服务编排'], 39 | ['/extend/decorator', '自定义注解'] 40 | ] 41 | }, 42 | ['/code-style', '代码风格'], 43 | ['/utils', '常用工具'], 44 | ['/contribut', '如何贡献'] 45 | ] 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/keycloak/src/keycloak.module.ts: -------------------------------------------------------------------------------- 1 | import * as config from 'config' 2 | import { Module, DynamicModule, Provider, Global } from '@nestjs/common' 3 | import * as KeycloakConnect from 'keycloak-connect' 4 | import { KeycloakConnectOptions } from './interface/keycloak-connect-options.interface' 5 | import { KEYCLOAK_CONNECT_OPTIONS, KEYCLOAK_INSTANCE } from './constants' 6 | 7 | @Global() 8 | @Module({}) 9 | export class KeycloakConnectModule { 10 | public static forRoot(opts: KeycloakConnectOptions): DynamicModule { 11 | return { 12 | module: KeycloakConnectModule, 13 | providers: [ 14 | { 15 | provide: KEYCLOAK_CONNECT_OPTIONS, 16 | useValue: opts 17 | }, 18 | this.keycloakProvider 19 | ], 20 | exports: [this.keycloakProvider] 21 | } 22 | } 23 | 24 | private static keycloakProvider: Provider = { 25 | provide: KEYCLOAK_INSTANCE, 26 | useFactory: (opts: KeycloakConnectOptions) => { 27 | const keycloakOpts: any = config.get('keycloak') 28 | const keycloak: any = new KeycloakConnect( 29 | { cookies: false }, 30 | keycloakOpts as any 31 | ) 32 | 33 | // Access denied is called, add a flag to request so our roles guard knows 34 | keycloak.accessDenied = (req: any, res: any, next: any) => { 35 | req.resourceDenied = true 36 | next() 37 | } 38 | 39 | // Disable keycloak redirectToLogin 40 | keycloak.redirectToLogin = (req: any) => { 41 | return false 42 | } 43 | return keycloak 44 | }, 45 | inject: [KEYCLOAK_CONNECT_OPTIONS] 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /sample/hello-world/test/users/users-register.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { request, testModule, prefix } from '../test-helper' 2 | import { UsersService } from '../../src/users/users.service' 3 | 4 | describe('AppController (e2e) with db ', () => { 5 | it('register with service mock', async () => { 6 | const usersService = testModule.get(UsersService) 7 | const spy = jest 8 | .spyOn(usersService, 'register') 9 | .mockImplementation(async () => ({ mock: true } as any)) 10 | 11 | console.log('prefix', prefix) 12 | const { body } = await request 13 | .post(prefix + '/users/register') 14 | .send({ name: 'nick', phone: '12345' }) 15 | .expect(201) 16 | .expect({ 17 | code: 0, 18 | message: 'success', 19 | data: { 20 | mock: true 21 | } 22 | }) 23 | console.log('body', body) 24 | expect(spy).toHaveBeenCalled() 25 | // 一定要 restore 不然会影响其他测试 26 | spy.mockRestore() 27 | }) 28 | 29 | it('register validate ', async () => { 30 | const { body } = await request 31 | .post(prefix + '/users/register') 32 | .send({ name: 'nick' }) 33 | .expect(400) 34 | console.log('body', body) 35 | expect(body.code).toEqual(400) 36 | expect(body.message).toEqual('phone should not be empty') 37 | }) 38 | 39 | it('register', async () => { 40 | const { body } = await request 41 | .post(prefix + '/users/register') 42 | .send({ name: 'nick', phone: '12345' }) 43 | .expect(201) 44 | expect(body.code).toEqual(0) 45 | expect(body.message).toEqual('success') 46 | expect(body.data.name).toEqual('nick') 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /packages/keycloak/src/guards/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CanActivate, 3 | ExecutionContext, 4 | Inject, 5 | Injectable, 6 | UnauthorizedException 7 | } from '@nestjs/common' 8 | import * as KeycloakConnect from 'keycloak-connect' 9 | import { KEYCLOAK_INSTANCE } from '../constants' 10 | 11 | /** 12 | * An authentication guard. Will return a 401 unauthorized when it is unable to 13 | * verify the JWT token or Bearer header is missing. 14 | */ 15 | @Injectable() 16 | export class AuthGuard implements CanActivate { 17 | constructor( 18 | @Inject(KEYCLOAK_INSTANCE) 19 | private keycloak: KeycloakConnect.Keycloak 20 | ) {} 21 | 22 | async canActivate(context: ExecutionContext): Promise { 23 | const request = context.switchToHttp().getRequest() 24 | if (request.url.match(/login$/)) return true 25 | const jwt = this.extractJwt(request.headers) 26 | const result = await this.keycloak.grantManager.validateAccessToken(jwt) 27 | 28 | if (typeof result === 'string') { 29 | // Attach user info object 30 | request.user = await this.keycloak.grantManager.userInfo(jwt) 31 | // createGrant for keycloak.protect check role 32 | const grant = await this.keycloak.grantManager.createGrant({ 33 | access_token: jwt 34 | }) 35 | request.kauth = { grant } 36 | return true 37 | } 38 | 39 | throw new UnauthorizedException() 40 | } 41 | 42 | extractJwt(headers: { [key: string]: string }) { 43 | if (!headers.authorization) { 44 | throw new UnauthorizedException() 45 | } 46 | 47 | const auth = headers.authorization.split(' ') 48 | 49 | // We only allow bearer 50 | if (auth[0].toLowerCase() !== 'bearer') { 51 | throw new UnauthorizedException() 52 | } 53 | 54 | return auth[1] 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /sample/nest-keycloak/src/users/users.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Req, Inject, Logger, UseGuards } from '@nestjs/common' 2 | import * as _ from 'lodash' 3 | import { Request } from 'express' 4 | import { UsersService } from './users.service' 5 | import { 6 | KEYCLOAK_INSTANCE, 7 | Roles, 8 | Keycloak, 9 | AuthGuard, 10 | RolesGuard 11 | } from '@kalengo/keycloak' 12 | 13 | @Controller('users') 14 | @UseGuards(RolesGuard) 15 | @UseGuards(AuthGuard) 16 | export class UsersController { 17 | logger = new Logger(UsersController.name) 18 | 19 | constructor( 20 | private readonly usersService: UsersService, 21 | @Inject(KEYCLOAK_INSTANCE) 22 | private readonly keycloak: Keycloak 23 | ) {} 24 | 25 | @Get() 26 | @Roles('realm:admin') 27 | async findAll(): Promise { 28 | return this.usersService.findAll() 29 | } 30 | 31 | @Get('/hello') 32 | async hello(): Promise { 33 | return 'Hello World!' 34 | } 35 | 36 | @Get('/info') 37 | @Roles('realm:user', 'realm:none') 38 | async protect(@Req() req: Request): Promise { 39 | const userInfo = (req as any).user 40 | console.log('userInfo', userInfo) 41 | return 'protect info' 42 | } 43 | 44 | @Get('/login') 45 | async login(): Promise { 46 | const username = 'user' 47 | const password = 'password' 48 | try { 49 | const grant = await this.keycloak.grantManager.obtainDirectly( 50 | username, 51 | password 52 | ) 53 | // console.info('grant', grant) 54 | const token: string = _.get(grant, 'access_token.token') 55 | this.logger.verbose('access_token', token) 56 | return { token } 57 | } catch (e) { 58 | this.logger.verbose('login fail', e) 59 | return 'login fail' 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /docs/database/mongoose.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarDepth: 2 3 | --- 4 | 5 | ## TypeORM 6 | 7 | [TypeORM](https://typeorm.io/#/) 是个优秀的 ORM 框架,快可以比肩 JPA 了,但是他们俩的共同问题是, 8 | 他们第一适配的是 SQL 类数据库,对 MongoDB 的适配马马虎虎。 9 | 10 | 同时考虑到,团队之前一直在使用 Mongoose ,继续使用熟悉的 Mongoose 对我们来说是个更好的选择。 11 | 12 | ## Nest 的 Mongoose 13 | Nest 在MongoDB 的使用上有两篇文档: 14 | [Mongo](https://docs.nestjs.com/techniques/mongodb) 15 | [Mongoose](https://docs.nestjs.com/recipes/mongodb) 16 | 17 | 效果嘛,挺糟糕的,老问题,mongoose 的 Schema 和 Interface 定义是分离的,要写很多重复代码 18 | 19 | ## Nest with Typegoose 20 | Typegoose 可以帮我们使用 Class 来定义一个 Model,社区中也已经有人做好了 [Nest 和 Typegoose 的适配](https://github.com/kpfromer/nestjs-typegoose) 21 | 在这个基础上,我们在结合 config,可以非常简单的完成配置。 22 | 23 | 在 config 中配置好 mongodb 的链接,支持多个 DB 24 | ```js 25 | module.exports = { 26 | port: process.env.PORT || 3000, 27 | mongodb: { 28 | debug: true, 29 | connections: [ 30 | { 31 | name: 'core', 32 | url: 'mongodb://localhost:57017/core', 33 | options: { 34 | useNewUrlParser: true, 35 | useUnifiedTopology: true 36 | } 37 | }, 38 | { 39 | name: 'app', 40 | url: 'mongodb://localhost:57017/app', 41 | options: { 42 | useNewUrlParser: true, 43 | useUnifiedTopology: true 44 | } 45 | } 46 | ] 47 | } 48 | } 49 | ``` 50 | 51 | 然后在 APPModule 中初始化 Typegoose 52 | 53 | ```ts 54 | import {Module} from '@nestjs/common' 55 | import {UsersModule} from './users/users.module' 56 | import {TypegooseModuleBuilder} from '@klg/mongoose' 57 | 58 | @Module({ 59 | imports: [ 60 | TypegooseModuleBuilder.forRoot(), 61 | UsersModule 62 | ] 63 | }) 64 | export class ApplicationModule { 65 | } 66 | ``` 67 | 68 | TypegooseModuleBuilder 是我们写好的一个工具类, 作用读取 config 的 uri 然后初始化 Typegoose。 69 | 70 | > **注意** TypegooseModuleBuilder 在 @klg/mongoose 中引入 71 | -------------------------------------------------------------------------------- /sample/nest-zeebe/src/service/recharge.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject } from '@nestjs/common' 2 | import { InjectModel } from 'nestjs-typegoose' 3 | import { OrderModel, Order, IOrderModel } from '../model/order.model' 4 | import { ZEEBE_CONNECTION_PROVIDER } from '@payk/nestjs-zeebe' 5 | import { ZBClient } from 'zeebe-node' 6 | import { revolver } from '../util' 7 | 8 | @Injectable() 9 | export class RechargeService { 10 | constructor( 11 | @InjectModel(Order) private readonly orderModel: OrderModel, 12 | @Inject(ZEEBE_CONNECTION_PROVIDER) private readonly zbClient: ZBClient 13 | ) {} 14 | 15 | async recharge(orderId: string, amount: number): Promise { 16 | const fOrder = await this.orderModel.findById(orderId) 17 | if (!fOrder) { 18 | const cOrder = new this.orderModel({ _id: orderId, amount }) 19 | await cOrder.save() 20 | } 21 | revolver() 22 | // 模拟第三方异步回调 23 | setTimeout(() => { 24 | this.zbClient 25 | .publishMessage({ 26 | correlationKey: orderId, 27 | messageId: orderId, 28 | name: Math.random() > 0.2 ? 'pay_success' : 'pay_fail', 29 | variables: {}, 30 | timeToLive: 10000 // seconds 31 | }) 32 | .then(console.log) 33 | .catch(console.error) 34 | }, 500) 35 | } 36 | 37 | async onSuccess(orderId: string): Promise { 38 | revolver() 39 | const order = await this.orderModel.findById(orderId) 40 | if (!order) throw new Error('order not found ' + orderId) 41 | await order.done() 42 | revolver() 43 | return order 44 | } 45 | 46 | async onFail(orderId: string): Promise { 47 | revolver() 48 | const order = await this.orderModel.findById(orderId) 49 | if (!order) throw new Error('order not found ' + orderId) 50 | await order.fail() 51 | revolver() 52 | return order 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /docs/extend/decorator.md: -------------------------------------------------------------------------------- 1 | 如何自定义注解呢? 2 | 3 | 4 | ## Controller 层 5 | 如果是在路由层面的注解,请看官方文档 [自定义路由参数装饰器](https://docs.nestjs.cn/7/customdecorators) 6 | 7 | ## Service 8 | 在 Service 层面做自定义注解,最大的问题是如何在注解里使用其他 Service 呢? 9 | 10 | StackOverFlow 网友和你有同样的疑问 [in-nest-js-how-to-get-a-service-instance-inside-a-decorator](https://stackoverflow.com/questions/52106406/in-nest-js-how-to-get-a-service-instance-inside-a-decorator) 11 | 12 | 在这个问题中,我们可以得到两个解决方案。 13 | 14 | 1. 在 Service A 引入 Service B,然后在注解里使用 Service B 15 | 16 | ```ts 17 | @Injectable() 18 | export class CatsService { 19 | constructor(public myService: MyService){} 20 | 21 | @CustomDecorator() 22 | foo(){} 23 | } 24 | 25 | export const CustomDecorator = (): MethodDecorator => { 26 | return ( 27 | target: Object, 28 | propertyKey: string | symbol, 29 | descriptor: PropertyDescriptor 30 | ) => { 31 | 32 | const originalMethod = descriptor.value; 33 | 34 | descriptor.value = function () { 35 | const serviceInstance = this; 36 | console.log(serviceInstance.myService); 37 | 38 | } 39 | 40 | return descriptor; 41 | } 42 | }; 43 | ``` 44 | 45 | 这里就要求被 CustomDecorator 注解的 Service 必须引入 MyService,不然程序就会出错, 46 | 这是一种约定式的绑定,非常不靠谱,不推荐。 47 | 48 | 2. 通过 Inject 方法(推荐) 49 | 50 | ```ts 51 | import { Inject } from '@nestjs/common'; 52 | export function yourDecorator() { 53 | const injectYourService = Inject(YourServiceClass); 54 | 55 | return (target: any, propertyKey: string, propertyDescriptor: PropertyDescriptor) => { 56 | // this is equivalent to have a constructor like constructor(yourservice: YourServiceClass) 57 | // note that this will injected to the instance, while your decorator runs for the class constructor 58 | injectYourService(target, 'yourservice'); 59 | 60 | // do something in you decorator 61 | 62 | // we use a ref here so we can type it 63 | const yourservice: YourServiceClass = this.yourservice; 64 | yourservice.someMethod(someParam); 65 | }; 66 | } 67 | ``` 68 | 69 | -------------------------------------------------------------------------------- /sample/hello-world/src/users/users.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Post, Body, Query } from '@nestjs/common' 2 | import { UsersService } from './users.service' 3 | import { IUserModel } from './model/user.model' 4 | import { IAccountModel } from './model/account.model' 5 | import { 6 | ApiOkResponse, 7 | ApiNotFoundResponse, 8 | ApiCreatedResponse, 9 | ApiResponse 10 | } from '@nestjs/swagger' 11 | import { UserDto, FindUsersRes, RegisterRes, FindAccountRes } from './users.dto' 12 | import { DateUtil, logger } from '@kalengo/utils' 13 | import { BusinessException } from '@kalengo/web' 14 | 15 | @Controller('users') 16 | export class UsersController { 17 | constructor(private readonly usersService: UsersService) {} 18 | 19 | @Post('/register') 20 | @ApiOkResponse({ 21 | description: 'find one account', 22 | type: RegisterRes 23 | }) 24 | async register(@Body() createUserDto: UserDto) { 25 | return await this.usersService.register(createUserDto) 26 | } 27 | 28 | @Get() 29 | @ApiCreatedResponse({ 30 | description: 'find user list', 31 | type: FindUsersRes 32 | }) 33 | async findAll(): Promise { 34 | return this.usersService.findAll() 35 | } 36 | 37 | @Get('/date') 38 | async date(): Promise { 39 | logger.info('get date') 40 | return DateUtil.format(new Date()) 41 | } 42 | 43 | @Get('/hello') 44 | @ApiOkResponse({ 45 | description: 'Hello World!' 46 | }) 47 | async hello(): Promise { 48 | return 'Hello World!' 49 | } 50 | 51 | @Get('/err') 52 | @ApiResponse({ status: 1, description: 'Forbidden.' }) 53 | async err(): Promise { 54 | throw new BusinessException() 55 | } 56 | 57 | @Get('/account') 58 | @ApiNotFoundResponse() 59 | @ApiOkResponse({ 60 | description: 'find one account', 61 | type: FindAccountRes 62 | }) 63 | async getAccountAndUser( 64 | @Query('userId') userId: string 65 | ): Promise { 66 | return this.usersService.getAccountAndUser(userId) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /sample/redis-redlock-cache/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nest-redis", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "", 6 | "license": "MIT", 7 | "scripts": { 8 | "prebuild": "rimraf dist", 9 | "build": "nest build", 10 | "start:dev": "NODE_ENV=dev nest start --watch", 11 | "start": "node dist/main", 12 | "lint": "tslint -c tslint.json '{src,test}/**/*.ts' --fix", 13 | "pretest:e2e": "npm run lint", 14 | "test": "jest", 15 | "test:cov": "jest --coverage", 16 | "test:e2e": "jest --config ./test/jest-e2e.json --runInBand" 17 | }, 18 | "dependencies": { 19 | "@kalengo/redis": "^1.0.3", 20 | "@kalengo/utils": "^0.1.5", 21 | "@kalengo/web": "^1.0.3", 22 | "@nestjs/common": "7.0.8", 23 | "@nestjs/core": "7.0.8", 24 | "@nestjs/platform-express": "7.0.8", 25 | "bluebird": "^3.7.2", 26 | "class-validator": "^0.12.1", 27 | "config": "^3.3.1", 28 | "lodash": "^4.17.15", 29 | "reflect-metadata": "^0.1.13", 30 | "rimraf": "^3.0.2", 31 | "rxjs": "^6.5.5" 32 | }, 33 | "devDependencies": { 34 | "@nestjs/cli": "^7.1.2", 35 | "@nestjs/schematics": "^7.0.0", 36 | "@nestjs/testing": "^7.0.7", 37 | "@types/bluebird": "^3.5.30", 38 | "@types/express": "^4.17.6", 39 | "@types/jest": "^25.2.1", 40 | "@types/lodash": "^4.14.149", 41 | "@types/node": "^13.11.1", 42 | "@types/supertest": "^2.0.8", 43 | "jest": "^25.3.0", 44 | "supertest": "^4.0.2", 45 | "ts-jest": "^25.3.1", 46 | "ts-loader": "^6.2.2", 47 | "ts-node": "^8.8.2", 48 | "tsconfig-paths": "^3.9.0", 49 | "tslint": "^6.1.1", 50 | "tslint-config-klg": "^2.0.0", 51 | "typescript": "^3.8.3" 52 | }, 53 | "jest": { 54 | "moduleFileExtensions": [ 55 | "js", 56 | "json", 57 | "ts" 58 | ], 59 | "rootDir": "src", 60 | "testRegex": ".spec.ts$", 61 | "transform": { 62 | "^.+\\.(t|j)s$": "ts-jest" 63 | }, 64 | "collectCoverageFrom": [ 65 | "**/*.(t|j)s" 66 | ], 67 | "coverageDirectory": "../coverage", 68 | "testEnvironment": "node" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /docs/contribut.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarDepth: 2 3 | --- 4 | 5 | 本脚手架包括三大内容: 6 | - 通用的npm 模块 packages 7 | - 项目示例 sample 8 | - klg-init 模板 9 | 10 | ## 开发 packages 11 | 为了方便用户升级,脚手架提供的特性大部分都会通过 @kalengo/xxx 这样的 npm 模块实现,用户只要模块版本就可以享受新功能。 12 | 13 | 因为是多模块开发,所以使用 [lerna](https://github.com/lerna/lerna) 这个库来帮忙管理和发布模块。 14 | 15 | 模块都存放在 packages 目录中,lerna 可以有效地帮你处理好模块之间的引入关系。 16 | 17 | ## 单元测试(TODO) 18 | 每个 packages 需要编写单元测试,保证稳定性,执行方法: 19 | ```bash 20 | npm run test 21 | ``` 22 | 23 | 24 | ## 集成测试 25 | 所有示例项目都存放在 sample 目录中,我们的每次修改都要保证示例的测试能够通过。 26 | 27 | 首先,把修改后的 packages build 到 sample 中 28 | ```bash 29 | npm run build 30 | ``` 31 | 32 | 然后执行集成测试, 这个命令会进入每一个 sample 并执行 `npm run test:e2e` 33 | ```bash 34 | bash run-tests 35 | ``` 36 | 37 | ## 发布 packages 38 | 以上测试都通过后,就可以执行发布 39 | 40 | ```bash 41 | npm run publish 42 | ``` 43 | 44 | 选择本次发布对应的版本,lerna 会帮你搞定发布流程。 45 | 46 | 47 | ## 验证 packages 48 | 为了验证 packages 是否可用,我们可以执行以下命令测试 49 | 50 | 先执行 `bash upgrade-packages.sh` 更新 sample 下的 @Kalengo 相关的包 51 | 52 | 再执行 `bash run-tests` 进行验证测试 53 | 54 | ## 发布脚手架模板 55 | 我们使用 klg-init 来生成脚手架,该脚手架需要一个模板。 56 | 57 | 模板存放位置 template/ 58 | 59 | 默认将 hello-world sample 转为模板,把 hello-world 项目更新到最新,使用最新的 npm 包之后。 60 | 61 | ```bash 62 | gulp move:template 63 | ``` 64 | 65 | 测试模板 66 | ```bash 67 | cd template 68 | npm run test 69 | ``` 70 | 71 | 该命令做了以下事情: 72 | 73 | - 将模板初始化为项目 74 | - npm install 75 | - npm test 76 | 77 | 注意:测试需要连接 mongodb, 修改 template/config/test.js 里的DB配置即可 78 | 79 | 测试通过就可以发布模板,执行 template/package.json 里的命令 80 | ```bash 81 | npm run publish 82 | ``` 83 | 84 | 项目模板将会以 npm 模块的形式发布出去, 模块名是 klg-nest-starter ,klg-init 会自动检测到最新模板。 85 | 86 | 注意:cnpm 同步 npm 仓库会有时延,你可以选择手动同步, 直接访问 https://npm.taobao.org/sync/klg-nest-starter 触发同步 87 | 88 | ## 更新文档 89 | 90 | 本项目的文档使用 [vuepress](https://vuepress.vuejs.org/zh/) , Vue 驱动的静态网站生成器。 91 | 使用 Markdown 编写,排版也非常简单,一键发布到 Github Pages。 92 | 93 | 执行一下命令,会打开本地文档预览,还会自动刷新。 94 | 95 | ```bash 96 | npm run docs:dev 97 | ``` 98 | 99 | 文档编写完成后,执行项目根目录的发布脚本 100 | 101 | ```bash 102 | bash deploy_doc.sh 103 | ``` 104 | 文档就更新到 Github Pages 了。 105 | -------------------------------------------------------------------------------- /docs/code-style.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebarDepth: 2 3 | --- 4 | 5 | ## Lint : Eslint 6 | 7 | TypeScript core team 在 2019 的时候宣称考虑到 eslint 比 tslint 有着更好的性能,将全面转用 eslint,不再更新 tslint。 8 | 9 | 所以我们也跟着转吧,所有配置都在脚手架中配好,默认在执行 `npm run test:e2e` 之前会执行一次 `npm run lint` 10 | 11 | ## Format : Prettier 12 | 13 | 而代码风格这里,将参考 Nest 的示例项目,使用 Prettier 统一 format. 14 | 15 | 如果你还不了解 Prettier,可以看看[这篇文章](https://zhuanlan.zhihu.com/p/81764012) 16 | 17 | ## 自动 Format 代码 18 | 19 | ### VS Code 20 | 21 | 上面提到的文章有提到如何安装插件,具体看文章 22 | 23 | ### Webstorm 24 | 25 | 在设置中找到 Prettier 的配置项,勾选 `Run on save for files` 即可 26 | 27 | ### Git hooks 28 | 安装 husky 这个工具 29 | 30 | ```shell script 31 | npm run i husky -D 32 | ``` 33 | 34 | 接着在package.json定义husky的配置: 35 | 36 | ```js 37 | "husky": { 38 | "hooks": { 39 | "pre-commit": "npm run format" 40 | } 41 | } 42 | ``` 43 | 我们在git的hook的阶段来执行相应的命令,比如上述的例子是在pre-commit这个hook也就是在提交之前进行lint的检测。 44 | 45 | ## Check commit message 46 | 47 | 我们将使用 [commitlint](https://github.com/conventional-changelog/commitlint) 来帮助我们检查 commit message。 48 | 49 | 安装对应的包: 50 | ```bash 51 | npm install @commitlint/cli @commitlint/config-conventional -D 52 | ``` 53 | 54 | 添加配置文件 55 | > commitlint.config.js 56 | 57 | ```js 58 | module.exports = { 59 | extends: ['@commitlint/config-conventional'], 60 | rules: { 61 | 'type-enum': [2, 'always', [ 62 | "feat", "fix", "docs", "style", "refactor", "perf", "test", "build", "ci", "chore", "revert" 63 | ]], 64 | 'subject-full-stop': [0, 'never'], 65 | 'subject-case': [0, 'never'] 66 | } 67 | } 68 | ``` 69 | 70 | 接着在package.json 新增 husky的配置: 71 | 72 | ```js 73 | "husky": { 74 | "hooks": { 75 | "commit-msg": "commitlint -e $HUSKY_GIT_PARAMS" 76 | } 77 | } 78 | ``` 79 | 80 | 这样就完成配置,当我们 commit 的时候,commitlint 就会帮我们完成格式校验。 81 | 82 | 一个正常的 commit message 看起来是这样的: 83 | 84 | > type(scope?): subject #scope is optional 85 | 86 | 或者这样: 87 | 88 | > fix(server): send cors headers 89 | 90 | > feat(blog): add comment section 91 | 92 | > docs: add comment section 93 | 94 | 不符合规则的 commit 将会被 block 95 | 96 | [完整项目示例](https://github.com/kaolalicai/klg-nest-starter/tree/master/sample/hello-world) 97 | 98 | -------------------------------------------------------------------------------- /packages/redis/src/redlock/redlock.module.ts: -------------------------------------------------------------------------------- 1 | import { DynamicModule, Module, Provider, Global, Inject } from '@nestjs/common' 2 | import { RedisService } from 'nestjs-redis' 3 | import { Redlock } from './Redlock' 4 | import { RedisModuleBuilder } from '../ioredis/redis.module.builder' 5 | import { BufferOptions } from './redlock.interface' 6 | import { RedlockService } from './redlock.service' 7 | import { 8 | REDLOCK_MODULE_OPTIONS, 9 | MUTEX_LOCK, 10 | BUFFER_LOCK 11 | } from './redlock.constants' 12 | 13 | export const createMutexLockClient = (): Provider => ({ 14 | provide: MUTEX_LOCK, 15 | useFactory: async (redisService: RedisService): Promise => { 16 | const client = redisService.getClient() 17 | const mutexLock = new Redlock(client, { 18 | retryCount: 0 19 | }) 20 | return mutexLock 21 | }, 22 | inject: [RedisService] 23 | }) 24 | 25 | export const createBufferLockClient = (): Provider => ({ 26 | provide: BUFFER_LOCK, 27 | useFactory: async ( 28 | redisService: RedisService, 29 | options: BufferOptions 30 | ): Promise => { 31 | const client = redisService.getClient() 32 | const defaultOptions = { 33 | retryCount: Math.floor((1000 * 60 * 15) / 400), // 重试次数 这里需要重试10分钟 34 | retryDelay: 400 // 重试间隔 35 | } 36 | const bufferLock = new Redlock( 37 | client, 38 | Object.assign(defaultOptions, options) 39 | ) 40 | return bufferLock 41 | }, 42 | inject: [RedisService, REDLOCK_MODULE_OPTIONS] 43 | }) 44 | 45 | @Global() 46 | @Module({ 47 | imports: [RedisModuleBuilder.forRoot()], 48 | providers: [RedlockService], 49 | exports: [RedlockService, BUFFER_LOCK, MUTEX_LOCK] 50 | }) 51 | export class RedlockModule { 52 | constructor( 53 | @Inject(REDLOCK_MODULE_OPTIONS) 54 | private readonly options: BufferOptions 55 | ) {} 56 | 57 | static forRoot(options?: BufferOptions): DynamicModule { 58 | return { 59 | module: RedlockModule, 60 | providers: [ 61 | createMutexLockClient(), 62 | createBufferLockClient(), 63 | { provide: REDLOCK_MODULE_OPTIONS, useValue: options || {} } 64 | ], 65 | exports: [RedlockService] 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /sample/redis-redlock-cache/test/users/users-redis.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { request } from '../test-helper' 2 | 3 | describe('users-redis.e2e-spec', () => { 4 | it('getAndSet', async () => { 5 | const res = await request.post('/users/getAndSet').expect(201) 6 | }) 7 | 8 | it('mutex', async () => { 9 | const results = await Promise.all([ 10 | request.get('/users/mutex').expect(200).expect([1, 2, 3, 4, 5]), 11 | request.get('/users/mutex').expect(500), 12 | request.get('/users/mutex').expect(500) 13 | ]) 14 | let [res1, res2, res3] = results 15 | console.log('res1', res1.body) 16 | console.log('res2', res2.body) 17 | console.log('res3', res3.body) 18 | }) 19 | 20 | it('buffer', async () => { 21 | const results = await Promise.all([ 22 | request.get('/users/buffer').expect(200).expect([1, 2, 3, 4, 5]), 23 | request.get('/users/buffer').expect(200).expect([1, 2, 3, 4, 5]), 24 | request.get('/users/buffer').expect(200).expect([1, 2, 3, 4, 5]) 25 | ]) 26 | let [res1, res2, res3] = results 27 | console.log('res1', res1.body) 28 | console.log('res2', res2.body) 29 | console.log('res3', res3.body) 30 | }) 31 | 32 | it('decorator Mutex', async () => { 33 | const results = await Promise.all([ 34 | request.get('/users/decorator/mutex').expect(200).expect([1, 2, 3, 4, 5]), 35 | request.get('/users/decorator/mutex').expect(500), 36 | request.get('/users/decorator/mutex').expect(500) 37 | ]) 38 | let [res1, res2, res3] = results 39 | console.log('res1', res1.body) 40 | console.log('res2', res2.body) 41 | console.log('res3', res3.body) 42 | }) 43 | 44 | it('decorator buffer', async () => { 45 | const results = await Promise.all([ 46 | request 47 | .get('/users/decorator/buffer') 48 | .expect(200) 49 | .expect([1, 2, 3, 4, 5]), 50 | request 51 | .get('/users/decorator/buffer') 52 | .expect(200) 53 | .expect([1, 2, 3, 4, 5]), 54 | request.get('/users/decorator/buffer').expect(200).expect([1, 2, 3, 4, 5]) 55 | ]) 56 | let [res1, res2, res3] = results 57 | console.log('res1', res1.body) 58 | console.log('res2', res2.body) 59 | console.log('res3', res3.body) 60 | }) 61 | }) 62 | -------------------------------------------------------------------------------- /packages/utils/src/NumberUtil.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash' 2 | 3 | export class NumberUtil { 4 | static defaultDigits = 2 5 | 6 | static setDigits(digits) { 7 | this.defaultDigits = digits 8 | } 9 | 10 | static add(a: number, b: number) { 11 | return this.fixNumPrecision(a + b) 12 | } 13 | 14 | static sub(a: number, b: number) { 15 | return this.fixNumPrecision(a - b) 16 | } 17 | 18 | static fixedNumUp(num: number, digits = NumberUtil.defaultDigits) { 19 | return parseFloat( 20 | Number(num + 5 / Math.pow(10, digits + 1)).toFixed(digits) 21 | ) 22 | } 23 | 24 | /** 25 | * 判断数字是否接近0 26 | * @param {number} a 27 | * @returns {boolean} 28 | */ 29 | static closeToZero(a: number) { 30 | // TODO 非空判断 31 | return a < 0.1 32 | } 33 | 34 | /** 35 | * 四舍五入 36 | * 1. 为什么不用 toFixed ?toFixed return string 这个方法 return number 37 | * 2. 为什么不用 Math.round ?不支持指定小数位 38 | * @param num 39 | * @param digits 默认2 40 | * @returns {number} 41 | */ 42 | static fixedNum(num: number, digits = NumberUtil.defaultDigits) { 43 | return _.round(num, digits) 44 | } 45 | 46 | /** 47 | * 修复 0.1 + 0.2 = 0.30000000000000004 的这类数字 48 | * @param {number} num 49 | * @returns {number} 50 | */ 51 | static fixNumPrecision(num: number): number { 52 | const left = Math.abs(num - this.fixedNum(num)) 53 | // 是 0.30000000000000004 的情况才 fix 54 | if (left < 1e-10) { 55 | return this.fixedNum(num) 56 | } 57 | return num 58 | } 59 | 60 | /** 61 | * 遍历 Object 的属性,找出所有 number 类型的属性并 fixNumPrecision 62 | * @param {object} obj 63 | * @returns object 64 | */ 65 | static fixObj(obj: T): T { 66 | if (!obj) return obj 67 | for (const key of Object.keys(obj)) { 68 | if (typeof obj[key] === 'number') { 69 | obj[key] = this.fixNumPrecision(obj[key]) 70 | } 71 | if (typeof obj[key] === 'object') { 72 | obj[key] = this.fixObj(obj[key]) 73 | } 74 | } 75 | return obj 76 | } 77 | 78 | /** 79 | * 截取 digits 位小数 不做四舍五入 80 | * @param num 81 | * @param digits 82 | * @returns {number} 83 | */ 84 | static cutNum(num: number, digits = NumberUtil.defaultDigits) { 85 | num = this.fixNumPrecision(num) 86 | return _.floor(num, digits) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /tools/gulp/tasks/packages.ts: -------------------------------------------------------------------------------- 1 | import {source} from '../config' 2 | import {task, watch, series, dest} from 'gulp' 3 | import {createProject} from 'gulp-typescript' 4 | import * as sourcemaps from 'gulp-sourcemaps' 5 | import * as log from 'fancy-log' 6 | 7 | // Has to be a hardcoded object due to build order 8 | const packages = { 9 | utils: createProject('packages/utils/tsconfig.json'), 10 | web: createProject('packages/web/tsconfig.json'), 11 | redis: createProject('packages/redis/tsconfig.json'), 12 | keycloak: createProject('packages/keycloak/tsconfig.json'), 13 | mongoose: createProject('packages/mongoose/tsconfig.json') 14 | } 15 | 16 | const modules = Object.keys(packages) 17 | 18 | const distId = process.argv.indexOf('--dist') 19 | const dist = distId < 0 ? source : process.argv[distId + 1] 20 | 21 | /** 22 | * Watches the packages/* folder and 23 | * builds the package on file change 24 | */ 25 | function defaultTask () { 26 | log.info('Watching files..') 27 | modules.forEach(packageName => { 28 | watch( 29 | [`${source}/${packageName}/**/*.ts`, `${source}/${packageName}/*.ts`], 30 | series(packageName) 31 | ) 32 | }) 33 | } 34 | 35 | /** 36 | * Builds the given package 37 | * @param packageName The name of the package 38 | */ 39 | function buildPackage (packageName: string) { 40 | return packages[packageName] 41 | .src() 42 | .pipe(packages[packageName]()) 43 | .pipe(dest(`${dist}/${packageName}`)) 44 | } 45 | 46 | /** 47 | * Builds the given package and adds sourcemaps 48 | * @param packageName The name of the package 49 | */ 50 | function buildPackageDev (packageName: string) { 51 | return packages[packageName] 52 | .src() 53 | .pipe(sourcemaps.init()) 54 | .pipe(packages[packageName]()) 55 | .pipe( 56 | sourcemaps.mapSources( 57 | (sourcePath: string) => './' + sourcePath.split('/').pop() 58 | ) 59 | ) 60 | .pipe(sourcemaps.write('.', {})) 61 | .pipe(dest(`${dist}/${packageName}`)) 62 | } 63 | 64 | modules.forEach(packageName => { 65 | task(packageName, () => buildPackage(packageName)) 66 | task(`${packageName}:dev`, () => buildPackageDev(packageName)) 67 | }) 68 | 69 | task('common:dev', series(modules.map(packageName => `${packageName}:dev`))) 70 | task('build', series(modules)) 71 | task('build:dev', series('common:dev')) 72 | task('default', defaultTask) 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 4 | 5 | Nest 脚手架,在开始使用之前,你需要对 Nest 有个基本的认识,特别是 Module 的概念。 6 | 7 | 本脚手架是 Kalengo 适配版本,针对 Kalengo 的实际需求对 Nest 做了适配. 8 | 9 | 详细使用文档: [Kalengo Nest Starter](https://kaolalicai.github.io/nest_doc/) 10 | 11 | 本脚手架包括三大内容: 12 | - 通用的npm 模块 packages 13 | - 项目示例 sample 14 | - klg-init 模板 15 | 16 | 接下来介绍一下完整的开发流程 17 | 18 | ## 通用的 npm 模块 19 | 为了方便用户升级,脚手架提供的特性大部分都会通过 @kalengo/xxx 这样的 npm 模块实现,用户只要模块版本就可以享受新功能。 20 | 21 | 因为是多模块开发,所以使用 [lerna](https://github.com/lerna/lerna) 这个库来帮忙管理和发布模块。 22 | 23 | 模块都存放在 packages 目录中,lerna 可以有效地帮你处理好模块之间的引入关系。 24 | 25 | ## 单元测试(TODO) 26 | 每个 packages 需要编写单元测试,保证稳定性,执行方法: 27 | ```bash 28 | npm run test 29 | ``` 30 | 31 | ## 集成测试 32 | 所有示例项目都存放在 sample 目录中,我们的每次修改都要保证示例的测试能够通过。 33 | 34 | 首先,把修改后的 packages build 到 sample 中 35 | ```bash 36 | npm run build 37 | ``` 38 | 39 | 然后执行集成测试, 这个命令会进入每一个 sample 并执行 `npm run test:e2e` 40 | ```bash 41 | bash run-tests 42 | ``` 43 | 44 | ## 发布 packages 45 | 以上测试都通过后,就可以执行发布 46 | 47 | ```bash 48 | npm run publish 49 | ``` 50 | 51 | 选择本次发布对应的版本,lerna 会帮你搞定发布流程。 52 | 53 | ## 验证 packages 54 | 为了验证 packages 是否可用,我们可以执行以下命令测试 55 | 56 | 先执行 `bash upgrade-packages.sh` 更新 sample 下的 @Kalengo 相关的包 57 | 58 | 再执行 `bash run-tests` 进行验证测试 59 | 60 | ## 发布脚手架模板 61 | 我们使用 klg-init 来生成脚手架,该脚手架需要一个模板。 62 | 63 | 模板存放位置 template/ 64 | 65 | 默认将 hello-world sample 转为模板,把 hello-world 项目更新到最新,使用最新的 npm 包之后。 66 | 67 | ```bash 68 | gulp move:template 69 | ``` 70 | 71 | 测试模板 72 | ```bash 73 | cd template 74 | npm run test 75 | ``` 76 | 77 | 该命令做了以下事情: 78 | 79 | - 将模板初始化为项目 80 | - npm install 81 | - npm test 82 | 83 | 注意:测试需要连接 mongodb, 修改 template/config/test.js 里的DB配置即可 84 | 85 | 测试通过就可以发布模板到 npm, 在 template 目录中执行 86 | 87 | ```bash 88 | npm publish --registry https://registry.npmjs.org 89 | ``` 90 | 91 | 项目模板将会以 npm 模块的形式发布出去, 模块名是 klg-nest-starter ,klg-init 会自动检测到最新模板。 92 | 93 | 注意:cnpm 同步 npm 仓库会有时延,你可以选择手动同步, 直接访问 https://npm.taobao.org/sync/klg-nest-starter 触发同步 94 | 95 | ## 更新文档 96 | 97 | 本项目的文档使用 [vuepress](https://vuepress.vuejs.org/zh/) , Vue 驱动的静态网站生成器。 98 | 使用 Markdown 编写,排版也非常简单,一键发布到 Github Pages。 99 | 100 | ### 本地编辑文档 101 | 执行一下命令,会打开本地文档预览,还会自动刷新。 102 | 103 | ```bash 104 | npm run docs:dev 105 | ``` 106 | 107 | 文档编写完成后,执行项目根目录的发布脚本 108 | 109 | ```bash 110 | bash deploy_doc.sh 111 | ``` 112 | 文档就更新到 Github Pages 了。 113 | -------------------------------------------------------------------------------- /packages/redis/src/redlock/redlock.ts: -------------------------------------------------------------------------------- 1 | import * as config from 'config' 2 | import * as _ from 'lodash' 3 | import * as Lock from 'redlock' 4 | import * as Redis from 'ioredis' 5 | import { logger } from '@kalengo/utils' 6 | import { LockOption } from './redlock.interface' 7 | 8 | const { prefix } = config.redis 9 | 10 | export class Redlock { 11 | redlock: Lock 12 | prefix: string = prefix || 'core:lock:' 13 | 14 | constructor(client: Redis.Redis, lockOption: LockOption) { 15 | const { retryCount = 0, retryDelay = 400 } = lockOption 16 | this.redlock = new Lock([client], { 17 | // the expected clock drift; for more details 18 | // see http://redis.io/topics/distlock 19 | driftFactor: 0.01, // time in ms 20 | // the max number of times Redlock will attempt 21 | // to lock a resource before erroring 22 | retryCount, 23 | // the time in ms between attempts 24 | retryDelay, // time in ms 25 | // the max time in ms randomly added to retries 26 | // to improve performance under high contention 27 | // see https://www.awsarchitectureblog.com/2015/03/backoff.html 28 | retryJitter: 400 // time in ms 29 | }) 30 | } 31 | 32 | /** 33 | * 分布式锁 34 | * @param func 待执行的函数 35 | * @param lockParam options 36 | */ 37 | async using( 38 | func: Function, 39 | lockParam: { resource: string; ttl?: number; autoUnLock?: boolean } 40 | ) { 41 | lockParam.ttl = _.toInteger(lockParam.ttl) || 1000 * 60 42 | if (lockParam.autoUnLock === undefined) lockParam.autoUnLock = true 43 | let lock = null 44 | try { 45 | lock = await this.redlock.lock( 46 | this.prefix + lockParam.resource, 47 | lockParam.ttl 48 | ) 49 | logger.info('lock success ', lock.resource) 50 | } catch (err) { 51 | logger.info('系统繁忙,请稍后再试 lock Error ', err) 52 | // if (this.lockConfig.handle) this.lockConfig.handle(err) 53 | throw new Error('系统繁忙,请稍后再试') 54 | } 55 | // 执行业务逻辑 56 | let result 57 | try { 58 | result = await func(lock) 59 | } catch (err) { 60 | // 出现业务错误也要解锁 61 | await lock.unlock() 62 | logger.info('business error,unlock success ', lock.resource) 63 | throw err 64 | } 65 | // 记得解锁 66 | if (lockParam.autoUnLock) { 67 | try { 68 | await lock.unlock() 69 | logger.info('unlock success ', lock.resource) 70 | } catch (err) { 71 | logger.error('unlock Error ', err) 72 | } 73 | } 74 | return result 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /sample/nest-simple/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nest-simple", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "", 6 | "license": "MIT", 7 | "scripts": { 8 | "prebuild": "rimraf dist", 9 | "build": "nest build", 10 | "start:dev": "NODE_ENV=dev nest start --watch", 11 | "start": "node dist/main", 12 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 13 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 14 | "pretest:e2e": "npm run lint", 15 | "test": "jest", 16 | "test:cov": "jest --coverage", 17 | "test:e2e": "jest --config ./test/jest-e2e.json --runInBand" 18 | }, 19 | "husky": { 20 | "hooks": { 21 | "pre-commit": "npm run format", 22 | "pre-push": "npm run lint", 23 | "commit-msg": "commitlint -e $HUSKY_GIT_PARAMS" 24 | } 25 | }, 26 | "dependencies": { 27 | "@kalengo/web": "^1.0.3", 28 | "@nestjs/common": "7.0.8", 29 | "@nestjs/core": "7.0.8", 30 | "@nestjs/platform-express": "7.0.8", 31 | "class-validator": "^0.11.1", 32 | "config": "^3.3.1", 33 | "lodash": "^4.17.15", 34 | "reflect-metadata": "^0.1.13", 35 | "rimraf": "^3.0.2", 36 | "rxjs": "^6.5.5" 37 | }, 38 | "devDependencies": { 39 | "@commitlint/cli": "^8.3.5", 40 | "@commitlint/config-conventional": "^8.3.4", 41 | "@nestjs/cli": "^7.1.2", 42 | "@nestjs/schematics": "^7.0.0", 43 | "@nestjs/testing": "^7.0.7", 44 | "@types/express": "^4.17.6", 45 | "@types/jest": "^25.2.1", 46 | "@types/lodash": "^4.14.149", 47 | "@types/mockjs": "^1.0.2", 48 | "@types/node": "^13.11.1", 49 | "@types/supertest": "^2.0.8", 50 | "@typescript-eslint/eslint-plugin": "^2.29.0", 51 | "@typescript-eslint/parser": "^2.29.0", 52 | "eslint": "^6.8.0", 53 | "eslint-config-prettier": "^6.11.0", 54 | "eslint-plugin-import": "^2.20.2", 55 | "husky": "^4.2.5", 56 | "jest": "^25.3.0", 57 | "mockjs": "^1.1.0", 58 | "prettier": "^2.0.5", 59 | "supertest": "^4.0.2", 60 | "ts-jest": "^25.3.1", 61 | "ts-loader": "^6.2.2", 62 | "ts-node": "^8.8.2", 63 | "tsconfig-paths": "^3.9.0", 64 | "typescript": "^3.8.3" 65 | }, 66 | "jest": { 67 | "moduleFileExtensions": [ 68 | "js", 69 | "json", 70 | "ts" 71 | ], 72 | "rootDir": "src", 73 | "testRegex": ".spec.ts$", 74 | "transform": { 75 | "^.+\\.(t|j)s$": "ts-jest" 76 | }, 77 | "collectCoverageFrom": [ 78 | "**/*.(t|j)s" 79 | ], 80 | "coverageDirectory": "../coverage", 81 | "testEnvironment": "node" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /sample/nest-keycloak/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nest-keycloak", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "", 6 | "license": "MIT", 7 | "scripts": { 8 | "prebuild": "rimraf dist", 9 | "build": "nest build", 10 | "start:dev": "NODE_ENV=dev nest start --watch", 11 | "start": "node dist/main", 12 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 13 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"config/**/*\"", 14 | "pretest:e2e": "npm run lint", 15 | "test": "jest", 16 | "test:cov": "jest --coverage", 17 | "test:e2e": "jest --config ./test/jest-e2e.json --runInBand" 18 | }, 19 | "husky": { 20 | "hooks": { 21 | "pre-commit": "npm run format", 22 | "pre-push": "npm run lint", 23 | "commit-msg": "commitlint -e $HUSKY_GIT_PARAMS" 24 | } 25 | }, 26 | "dependencies": { 27 | "@kalengo/keycloak": "^1.0.0", 28 | "@kalengo/web": "^1.0.3", 29 | "@nestjs/common": "7.0.8", 30 | "@nestjs/core": "7.0.8", 31 | "@nestjs/platform-express": "7.0.8", 32 | "class-validator": "^0.11.1", 33 | "config": "^3.3.1", 34 | "lodash": "^4.17.15", 35 | "reflect-metadata": "^0.1.13", 36 | "rimraf": "^3.0.2", 37 | "rxjs": "^6.5.5" 38 | }, 39 | "devDependencies": { 40 | "@commitlint/cli": "^8.3.5", 41 | "@commitlint/config-conventional": "^8.3.4", 42 | "@nestjs/cli": "^7.1.2", 43 | "@nestjs/schematics": "^7.0.0", 44 | "@nestjs/testing": "^7.0.7", 45 | "@types/express": "^4.17.6", 46 | "@types/jest": "^25.2.1", 47 | "@types/lodash": "^4.14.149", 48 | "@types/mockjs": "^1.0.2", 49 | "@types/node": "^13.11.1", 50 | "@types/supertest": "^2.0.8", 51 | "@typescript-eslint/eslint-plugin": "^2.29.0", 52 | "@typescript-eslint/parser": "^2.29.0", 53 | "eslint": "^6.8.0", 54 | "eslint-config-prettier": "^6.11.0", 55 | "eslint-plugin-import": "^2.20.2", 56 | "husky": "^4.2.5", 57 | "jest": "^25.3.0", 58 | "mockjs": "^1.1.0", 59 | "prettier": "^2.0.5", 60 | "supertest": "^4.0.2", 61 | "ts-jest": "^25.3.1", 62 | "ts-loader": "^6.2.2", 63 | "ts-node": "^8.8.2", 64 | "tsconfig-paths": "^3.9.0", 65 | "typescript": "^3.8.3" 66 | }, 67 | "jest": { 68 | "moduleFileExtensions": [ 69 | "js", 70 | "json", 71 | "ts" 72 | ], 73 | "rootDir": "src", 74 | "testRegex": ".spec.ts$", 75 | "transform": { 76 | "^.+\\.(t|j)s$": "ts-jest" 77 | }, 78 | "collectCoverageFrom": [ 79 | "**/*.(t|j)s" 80 | ], 81 | "coverageDirectory": "../coverage", 82 | "testEnvironment": "node" 83 | } 84 | } 85 | --------------------------------------------------------------------------------