├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode └── launch.json ├── README.md ├── doc ├── readme.md └── todo.md ├── e2e ├── jest-e2e.json └── photo │ └── photo.e2e-spec.ts ├── gulpfile.js ├── jest.json ├── nestfy.json ├── package.json ├── src ├── lib │ ├── config │ │ ├── index.ts │ │ └── interface.ts │ ├── constants.ts │ ├── decorators │ │ ├── auth.decorator.ts │ │ ├── config │ │ │ ├── symbols.ts │ │ │ └── task.ts │ │ ├── index.ts │ │ └── task.decorator.ts │ ├── filters │ │ └── error.filter.ts │ ├── guards │ │ └── auth.guard.ts │ ├── index.ts │ ├── interceptors │ │ ├── exception.interceptor.ts │ │ ├── logging.interceptor.ts │ │ ├── timeout.interceptor.ts │ │ └── transform.interceptor.ts │ ├── middlewares │ │ ├── request-log.middleware.ts │ │ └── verify-token.middleware.ts │ ├── pipes │ │ ├── index.ts │ │ └── validation.pipe.ts │ ├── tsconfig.json │ └── utils │ │ ├── app.util.ts │ │ ├── index.ts │ │ ├── log.util.ts │ │ ├── response.util.ts │ │ └── token.util.ts ├── testing │ ├── common │ │ └── config │ │ │ ├── config.dev.ts │ │ │ ├── config.prod.ts │ │ │ ├── errors.ts │ │ │ ├── index.ts │ │ │ └── setting.ts │ ├── modules │ │ ├── app.module.ts │ │ └── photo │ │ │ ├── dto │ │ │ ├── create-photo.dto.ts │ │ │ └── query-photos.dto.ts │ │ │ ├── photo.controller.spec.ts │ │ │ ├── photo.controller.ts │ │ │ ├── photo.entity.ts │ │ │ ├── photo.module.ts │ │ │ └── photo.service.ts │ ├── server.ts │ └── tsconfig.json └── tsconfig.base.json ├── tsconfig.json ├── tslint.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # modules 2 | node_modules 3 | dist 4 | deploy 5 | build 6 | 7 | # jest ut files 8 | coverage 9 | 10 | # OSX 11 | # 12 | .DS_Store 13 | 14 | # npm debug file 15 | npm-debug.log 16 | 17 | # logs 18 | logs 19 | 20 | *.suo 21 | 22 | # 过滤数据库文件、sln解决方案文件、配置文件 23 | *.mdb 24 | *.ldb 25 | 26 | # 过滤文件夹Debug,Release,obj 27 | Debug/ 28 | Release/ 29 | obj/ -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | src/**/*.d.ts 2 | src/**/*.js -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "printWidth": 120, 4 | "trailingComma": "all" 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "outputCapture": "std", 11 | "name": "启动程序", 12 | "runtimeArgs": ["-r", "ts-node/register"], 13 | "args": ["${workspaceFolder}/src/testing/server.ts"] 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nestfy 框架 2 | 3 | 一个基于 Nestjs 的 Restful 后台框架。 4 | 5 | > 本框架旨在帮助开发人员快速搭建一套成型的企业后台 REST 应用。
6 | > 使用本框架之前需要先了解 [Nestjs 框架](https://docs.nestjs.com) 7 | > 如何使用本框架请参考 [Nestfy的使用例子工程](https://github.com/qinyang1980/nestfy-starter) 8 | --- 9 | 10 | [TOC] 11 | 12 | ## 如何安装 13 | 14 | npm: 15 | `npm install --save nestfy` 16 | 17 | yarn: 18 | `yarn add nestfy` 19 | 20 | ## 使用方法 21 | 22 | ### 日志 23 | 24 | 本框架集成了 log4js 日志模块,开启日志功能需要在配置文件中开启日志功能,并进行配置。 25 | 26 | 如下给出几种工程中常用的配置: 27 | 28 | > 日志只输出到 console 控制台 29 | 30 | ```typescript 31 | "logging": { 32 | "enable": true, 33 | 34 | // 日志配置, 35 | "configuration": { 36 | "appenders": { 37 | "console": { "type": "stdout" } 38 | }, 39 | "categories": { 40 | "default": { "appenders": ["console"], "level": "info" } 41 | } 42 | } 43 | }, 44 | ``` 45 | 46 | > 日志输出到控制台,并且还输出到 2 个文件,一个文件是记录所有日志,一个文件只记录错误日志 47 | > 48 | > 日志文件如果超过 maxLogSize 设置的容量,则会产生新文件 49 | > 50 | > 旧的日志文件会自动进行备份,备份文件会被压缩为 gz 格式 51 | > 52 | > backups 设置保存日志文件的个数,多出的则被删除 53 | 54 | ```typescript 55 | "logging": { 56 | "enable": true, 57 | 58 | // 日志配置 59 | "configuration": { 60 | "appenders": { 61 | "txt": { 62 | "type": "file", 63 | "filename": "./logs/app.log", 64 | "maxLogSize": 10485760, 65 | "backups": 10, 66 | "compress": true 67 | }, 68 | "err": { "type": "file", "filename": "./logs/err.log", "maxLogSize": 10485760, "backups": 5, "compress": true }, 69 | "out": { "type": "stdout" } 70 | }, 71 | "categories": { 72 | "default": { "appenders": ["out", "txt"], "level": "debug" }, 73 | "error": { "appenders": ["err"], "level": "error" } 74 | } 75 | } 76 | } 77 | ``` 78 | 79 | > 日志输出到控制台和一个文件中 80 | > 每天会产生一个新的日志文件,备份的旧文件会以日期命名 81 | 82 | ```typescript 83 | "logging": { 84 | "enable": true, 85 | 86 | // 日志配置, 87 | "configuration": { 88 | "appenders": { 89 | "txt": { "type": "dateFile", "filename": "./logs/nestfy.log" }, 90 | "out": { "type": "stdout" } 91 | }, 92 | "categories": { 93 | "default": { "appenders": ["out", "txt"], "level": "info" } 94 | } 95 | } 96 | }, 97 | ``` 98 | 99 | 更详细的配置项,请参考 [log4js 官方文档](https://log4js-node.github.io/log4js-node/) 100 | 101 | ### 跨域 102 | 103 | 允许所有请求 <推荐配置> 104 | 105 | ```typescript 106 | "cors": { 107 | "enable": true, 108 | "configuration": {} 109 | } 110 | ``` 111 | 112 | 只允许从 example.com 过来的请求 113 | 114 | ```typescript 115 | "cors": { 116 | "enable": true, 117 | "configuration": { 118 | "origin": "http://example.com" 119 | } 120 | } 121 | ``` 122 | 123 | 更详细的配置参数,请参考 CORS 官网 `https://github.com/expressjs/cors` 124 | 125 | ### 参数校验 126 | 127 | web 请求的参数可以自动被校验。 128 | 本框架可以从全局来进行控制,是否启用 DTO 参数校验的功能。 129 | 130 | ```typescript 131 | "validation": { 132 | "enable": true, 133 | "configuration": { 134 | "skipMissingProperties": true 135 | } 136 | } 137 | ``` 138 | 139 | 具体的配置参数,可以参考[class-validator 官网](https://github.com/typestack/class-validator) 140 | 141 | 只有校验参数成功后,才进入 Controller 层;如果校验失败,则框架会抛出 Validation 异常,自动返回  错误的 JSON 结果给客户端。 142 | 143 | 示例如下: 144 | 145 | > query-photo.dto.ts 146 | 147 | ```typescript 148 | import { ApiModelProperty } from '@nestjs/swagger'; 149 | import { Type } from 'class-transformer'; 150 | import { IsDefined, IsInt, IsNotEmpty, IsNumberString, IsString } from 'class-validator'; 151 | 152 | export class QueryPhotosDto { 153 | @ApiModelProperty({ description: '查询偏移量', required: true }) 154 | @IsDefined() 155 | @Type(() => Number) 156 | @IsInt() 157 | public offset: number; 158 | 159 | @ApiModelProperty({ description: '查询个数', required: true }) 160 | @IsDefined() 161 | @Type(() => Number) 162 | @IsInt() 163 | public limit: number; 164 | 165 | @ApiModelProperty({ description: '名称', required: false }) 166 | @IsString() 167 | @IsNotEmpty() 168 | public readonly name: string; 169 | } 170 | ``` 171 | 172 | > 错误请求(不符合如上 DTO 要求的请求),被框架处理后的返回结果: 173 | 174 | ```json 175 | { 176 | "success": false, 177 | "status": 400, 178 | "timestamp": "2019-01-24T09:57:44.261Z", 179 | "message": "You have an error in your request's body. Check 'errors' field for more details!", 180 | "errors": [ 181 | { 182 | "target": {}, 183 | "property": "offset", 184 | "children": [], 185 | "constraints": { 186 | "isDefined": "offset should not be null or undefined" 187 | } 188 | }, 189 | { 190 | "target": {}, 191 | "property": "limit", 192 | "children": [], 193 | "constraints": { 194 | "isDefined": "limit should not be null or undefined" 195 | } 196 | } 197 | ], 198 | "path": "/api/rest/v1/photos" 199 | } 200 | ``` 201 | 202 | ### 自动记录 web 请求时间 203 | 204 | 项目开发的过程中,有时候需要在日志中打印出 web 请求到达的时间,后台处理消耗的时长,请求返回的时间等信息。 205 | 使用本框架,直接在`nestfy.json`配置文件中将如下开关开启即可。 206 | 207 | ```typescript 208 | "request": { 209 | // 是否自动记录每次请求的耗时时长(ms) 210 | "traceRequestDuration": true 211 | } 212 | ``` 213 | 214 | ### auth 模块 215 | 216 | 该模块适用于基于 token 认证的后台项目,如果使用 session 机制,请关闭此开关。 217 |  开关开启后,会在全局范围内启用 token 认证,每一个 web 请求,都会去解析 header 中是否包含 token 信息,解析后的结果会存放在 express.Request 对象的由 decodedTag 指定的属性中。如下配置,就是存放在 req.user 中。 218 | 219 | ```typescript 220 | "auth": { 221 | "enable": false, // 是否启用,启用后请求如果不携带token信息,将无法调用所有接口 222 | "headerTag": "x-access-token", // 放在header里面的tag的名称 223 | "secret": "i6r5LgMJdoa5trlM", // 加密的密钥 224 | "expiresIn": "24h", // 失效时长 225 | "decodedTag": "user" // 解密后放在req对象里面的字段的名称 226 | } 227 | ``` 228 | 229 | 有些接口,在用户未登录的时候,也需要能够访问,这时候可以使用 @Auth 注解 230 | 231 | 放在函数前面,用于禁止该路由的 token 验证 232 | 233 | ```typescript 234 | import { Auth } from 'nestfy'; 235 | ... 236 | @Auth(false) 237 | @Get('/extends') 238 | public async extends(@Query() query: any): Promise { 239 | return this._admAwardService.extends(query.date); 240 | } 241 | ``` 242 | 243 | ### swagger 支持 244 | 245 | 开启功能可以使用swagger自动产生API文档,可用配置如下,swagger的使用方法请看[这里](https://docs.nestjs.com/recipes/swagger) 246 | 247 | ```typescript 248 | "swagger": { 249 | "enable": true, // 是否启用swagger文档功能 250 | "title": "nestfy-starter", // 文档的title 251 | "description": "The photo API description", // 文档的描述 252 | "schemas": "http", // 接口是否安全,仅支持(http 和 https两种) 253 | "version": "1.0", // 文档的版本 254 | "email": "qinyang_1980@qq.com", // 作者的联系email 255 | "path": "/doc" // 文档路由 256 | } 257 | ``` 258 | 259 | ### AppUtil 260 | 261 | 一个工具类, 262 | 263 | 请将名为 nestfy.json 的配置文件放在工程的根目录,模板如下: 264 | 265 | !> 请带注释一起 copy,nestfy 可以识别 jsonc 格式的配置文件。 266 | 267 | > nestfy.json 268 | 269 | ```typescript 270 | { 271 | // 应用相关配置项 272 | "app": { 273 | // 服务的端口号 274 | "port": 3000, 275 | 276 | // 服务启动成功后,打印此日志 277 | "setUpMsg": "Nestfy server started", 278 | 279 | // 路由前缀 280 | "apiPrefix": { 281 | "enable": true, // 是否启用路由前缀 282 | "prefix": "/api/rest" // 前缀 283 | } 284 | }, 285 | // 日志相关配置项 286 | "logging": { 287 | // 是否启用日志 288 | "enable": true, 289 | 290 | // 日志配置,具体可以参考log4js的官方文档(https://log4js-node.github.io/log4js-node/) 291 | "configuration": { 292 | "appenders": { 293 | "txt": { "type": "dateFile", "filename": "./logs/nestfy.log" }, 294 | "out": { "type": "stdout" } 295 | }, 296 | "categories": { 297 | "default": { "appenders": ["out", "txt"], "level": "info" } 298 | } 299 | } 300 | }, 301 | // web请求相关配置项 302 | "request": { 303 | // 是否自动记录每次请求的耗时时长(ms) 304 | "traceRequestDuration": true, 305 | 306 | // 跨域配置 307 | "cors": { 308 | // 是否启用跨域 309 | "enable": true, 310 | 311 | // 具体配置请参考Cors官方网站(https://github.com/expressjs/cors) 312 | "configuration": {} 313 | }, 314 | 315 | // bodyParser第三方中间件配置 316 | "bodyParser": { 317 | // 是否启用bodyParser 318 | "enable": true, 319 | 320 | // Body的容量限制(mb) 321 | "limit": "5mb" 322 | }, 323 | // DTO类自动校验相关配置 324 | "validation": { 325 | "enable": true, // 是否启用自动校验功能 326 | "skipMissingProperties": true // 不存在的属性,是否跳过校验 327 | }, 328 | // 接口权限控制相关配置 329 | "auth": { 330 | "enable": false, // 是否启用,启用后请求如果不携带token信息,将无法调用所有接口 331 | "headerTag": "x-access-token", // 放在header里面的tag的名称 332 | "secret": "i6r5LgMJdoa5trlM", // 加密的密钥 333 | "expiresIn": "24h", // 失效时长 334 | "decodedTag": "user" // 解密后放在req对象里面的字段的名称 335 | } 336 | }, 337 | // web返回相关配置项 338 | "response": { 339 | // 成功情况配置 340 | "success": { 341 | // message字段的默认文本 342 | "defaultMessage": "执行成功!", 343 | 344 | // success字段配置 345 | "successField": { 346 | "enable": true, // 是否启用success字段 347 | "name": "success" // 字段的名字 348 | }, 349 | 350 | // status字段配置 351 | "statusField": { 352 | "enable": true, // 是否启用status字段 353 | "name": "status" // 字段的名字 354 | } 355 | }, 356 | // 失败情况配置 357 | "failure": { 358 | // message字段的默认文本 359 | "defaultMessage": "执行失败!", 360 | 361 | // success字段配置 362 | "successField": { 363 | "enable": true, // 是否启用success字段 364 | "name": "success" // 字段的名字 365 | }, 366 | // status字段配置 367 | "statusField": { 368 | "enable": true, // 是否启用status字段 369 | "name": "status" // 字段的名字 370 | } 371 | } 372 | }, 373 | // swagger相关配置 374 | "swagger": { 375 | "enable": true, // 是否启用swagger文档功能 376 | "title": "nestfy-starter", // 文档的title 377 | "description": "The photo API description", // 文档的描述 378 | "schemas": "http", // 接口是否安全,仅支持(http 和 https两种) 379 | "version": "1.0", // 文档的版本 380 | "email": "qinyang_1980@qq.com", // 作者的联系email 381 | "path": "/doc" // 文档路由 382 | } 383 | } 384 | ``` 385 | 386 | 然后在程序中导入 AppUtil 类,如下使用方式: 387 | 388 | ```typescript 389 | import { AppUtil } from 'nestfy'; 390 | import { ApplicationModule } from './modules/app.module'; 391 | 392 | AppUtil.bootstrap(ApplicationModule); 393 | ``` 394 | 395 | 这样,一条语句即完成了应用类的创建,运行成功后,还会打印出  成功日志。 396 | -------------------------------------------------------------------------------- /doc/readme.md: -------------------------------------------------------------------------------- 1 | # 后台服务 2 | 3 | 1.install 4 | 5 | 安装所有依赖包 6 | install yarn 7 | yarn 8 | 9 | 2.run 10 | 11 | 运行开发模式 12 | yarn run start 13 | 14 | 运行编译后的产品代码 15 | yarn run start:prod 16 | 17 | 3.tslint 18 | 19 | tslint检查 20 | yarn run lint 21 | 22 | 4.testing 23 | 24 | 运行单元测试 25 | yarn run test 26 | 27 | 运行单元测试并监视代码 28 | yarn run test:watch 29 | 30 | 运行单元测试并输出覆盖率报告 31 | yarn run test:coverage 32 | 33 | 运行端对端测试 34 | yarn run e2e 35 | 36 | 运行端对端测试并监视代码 37 | yarn run e2e:watch 38 | 39 | 5.package 40 | 41 | 编译打包 42 | yarn run build 43 | -------------------------------------------------------------------------------- /doc/todo.md: -------------------------------------------------------------------------------- 1 |  2 | # TODOs 3 | 4 | - 考虑配置文件 5 | - 单元测试 6 | 7 | - npm publish --registry http://192.168.1.203:7000 -------------------------------------------------------------------------------- /e2e/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["ts", "tsx", "js", "json"], 3 | "transform": { 4 | "^.+\\.tsx?$": "ts-jest" 5 | }, 6 | "testRegex": "/e2e/.*\\.(e2e-test|e2e-spec).(ts|tsx|js)$", 7 | "collectCoverageFrom": ["src/**/*.{js,jsx,tsx,ts}", "!**/node_modules/**", "!**/vendor/**"], 8 | "coverageReporters": ["json", "lcov"] 9 | } 10 | -------------------------------------------------------------------------------- /e2e/photo/photo.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common'; 2 | import { Test } from '@nestjs/testing'; 3 | import * as request from 'supertest'; 4 | import { PhotoModule } from '../../src/app/modules/photo/photo.module'; 5 | import { PhotoService } from '../../src/app/modules/photo/photo.service'; 6 | 7 | describe('Photos', () => { 8 | let app: INestApplication; 9 | const photoService = { findAll: () => ['test'] }; 10 | 11 | beforeAll(async () => { 12 | const module = await Test.createTestingModule({ 13 | imports: [PhotoModule] 14 | }) 15 | .overrideProvider(PhotoService) 16 | .useValue(photoService) 17 | .compile(); 18 | 19 | app = module.createNestApplication(); 20 | await app.init(); 21 | }); 22 | 23 | it(`/GET photos`, () => { 24 | return request(app.getHttpServer()) 25 | .get('/photos') 26 | .expect(200) 27 | .expect({ 28 | data: photoService.findAll() 29 | }); 30 | }); 31 | 32 | afterAll(async () => { 33 | await app.close(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const gulp_tslint = require('gulp-tslint'); 3 | const del = require('del'); 4 | const shell = require('gulp-shell'); 5 | const fs = require('fs-extra'); 6 | const path = require('path'); 7 | 8 | //////////////////////////////////////////////////// 9 | 10 | // 通过NODE_ENV来设置环境变量,如果没有指定则默认为开发环境 11 | const DEV = 'development'; 12 | const PRO = 'production'; 13 | 14 | let env = process.env.NODE_ENV || DEV; 15 | env = env.toLowerCase(); 16 | 17 | console.log(`The env is (${env})`); 18 | 19 | //////////////////////////////////////////////////// 20 | 21 | gulp.task('default', ['build']); 22 | gulp.task('start', ['build'], shell.task(['ts-node src/testing/server.ts'])); 23 | gulp.task('build', ['deploy'], shell.task(['yarn run tsc'])); 24 | 25 | gulp.task('start:prod', ['build'], shell.task(['node build/testing/server.js'])); 26 | gulp.task('test', ['tslint'], shell.task(['jest --config=jest.json'])); 27 | gulp.task('test:watch', ['tslint'], shell.task(['jest --watch --config=jest.json'])); 28 | gulp.task('test:coverage', ['tslint'], shell.task(['jest --config=jest.json --coverage --coverageDirectory=coverage'])); 29 | gulp.task('e2e', ['tslint'], shell.task(['jest --config=e2e/jest-e2e.json'])); 30 | gulp.task('e2e:watch', ['tslint'], shell.task(['jest --watch --config=e2e/jest-e2e.json'])); 31 | 32 | gulp.task('tslint', () => { 33 | return gulp 34 | .src(['./src/**/*.ts', '!**/*.d.ts', '!node_modules/**']) 35 | .pipe( 36 | gulp_tslint({ 37 | formatter: 'verbose' 38 | }) 39 | ) 40 | .pipe( 41 | gulp_tslint.report({ 42 | emitError: true, 43 | summarizeFailureOutput: true 44 | }) 45 | ); 46 | }); 47 | 48 | //////////////////////////////////////////////////// 49 | 50 | function deploy() { 51 | const distFolder = path.join(__dirname, './build/lib'); 52 | const appPackageConfig = path.join(__dirname, './package.json'); 53 | const readme = path.join(__dirname, './README.md'); 54 | 55 | fs.copySync(readme, `${distFolder}/README.md`); 56 | 57 | const packageConfig = fs.readJsonSync(appPackageConfig); 58 | delete packageConfig.devDependencies; 59 | delete packageConfig.scripts; 60 | fs.outputJson(distFolder + '/package.json', packageConfig); 61 | } 62 | 63 | /////////////////////////////////////////////////// 64 | 65 | gulp.task('clean', () => { 66 | return del('./build/', { force: true }); 67 | }); 68 | 69 | gulp.task('deploy', ['clean', 'tslint'], () => { 70 | return deploy(); 71 | }); 72 | -------------------------------------------------------------------------------- /jest.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["ts", "tsx", "js", "json"], 3 | "transform": { 4 | "^.+\\.tsx?$": "ts-jest" 5 | }, 6 | "testRegex": "/src/.*\\.(test|spec).(ts|tsx|js)$", 7 | "collectCoverageFrom": ["src/**/*.{js,jsx,tsx,ts}", "!**/node_modules/**", "!**/vendor/**"], 8 | "coverageReporters": ["json", "lcov"] 9 | } 10 | -------------------------------------------------------------------------------- /nestfy.json: -------------------------------------------------------------------------------- 1 | { 2 | // 应用相关配置项 3 | "app": { 4 | // 服务的端口号 5 | "port": 12000, 6 | 7 | // 服务启动成功后,打印此日志 8 | "setUpMsg": "Nestfy server started", 9 | 10 | // 路由前缀 11 | "apiPrefix": { 12 | "enable": true, // 是否启用路由前缀 13 | "prefix": "/api/rest" // 前缀 14 | } 15 | }, 16 | // 日志相关配置项 17 | "logging": { 18 | // 是否启用日志 19 | "enable": true, 20 | 21 | // 日志配置,具体可以参考log4js的官方文档(https://log4js-node.github.io/log4js-node/) 22 | "configuration": { 23 | "appenders": { 24 | "txt": { "type": "dateFile", "filename": "./logs/nestfy.log" }, 25 | "out": { "type": "stdout" } 26 | }, 27 | "categories": { 28 | "default": { "appenders": ["out", "txt"], "level": "info" } 29 | } 30 | } 31 | }, 32 | // web请求相关配置项 33 | "request": { 34 | // 是否自动记录每次请求的耗时时长(ms) 35 | "traceRequestDuration": true, 36 | 37 | // 跨域配置 38 | "cors": { 39 | // 是否启用跨域 40 | "enable": true, 41 | 42 | // 具体配置请参考Cors官方网站(https://github.com/expressjs/cors) 43 | "configuration": { 44 | "origin": "http://example.com" 45 | } 46 | }, 47 | 48 | // bodyParser第三方中间件配置 49 | "bodyParser": { 50 | // 是否启用bodyParser 51 | "enable": true, 52 | 53 | // Body的容量限制(mb) 54 | "limit": "5mb" 55 | }, 56 | 57 | // DTO类自动校验相关配置 58 | "validation": { 59 | "enable": true, // 是否启用自动校验功能 60 | 61 | // 属性校验的配置项 62 | "configuration": { 63 | "skipMissingProperties": true 64 | } 65 | }, 66 | 67 | // 接口权限控制相关配置 68 | "auth": { 69 | "enable": false, // 是否启用,启用后请求如果不携带token信息,将无法调用所有接口 70 | "headerTag": "x-access-token", // 放在header里面的tag的名称 71 | "secret": "i6r5LgMJdoa5trlM", // 加密的密钥 72 | "expiresIn": "24h", // 失效时长 73 | "decodedTag": "user" // 解密后放在req对象里面的字段的名称 74 | } 75 | }, 76 | // web返回相关配置项 77 | "response": { 78 | // 成功情况配置 79 | "success": { 80 | // message字段的默认文本 81 | "defaultMessage": "执行成功!", 82 | 83 | // success字段配置 84 | "successField": { 85 | "enable": true, // 是否启用success字段 86 | "name": "success" // 字段的名字 87 | }, 88 | 89 | // status字段配置 90 | "statusField": { 91 | "enable": true, // 是否启用status字段 92 | "name": "status" // 字段的名字 93 | } 94 | }, 95 | // 失败情况配置 96 | "failure": { 97 | // message字段的默认文本 98 | "defaultMessage": "执行失败!", 99 | 100 | // success字段配置 101 | "successField": { 102 | "enable": true, // 是否启用success字段 103 | "name": "success" // 字段的名字 104 | }, 105 | // status字段配置 106 | "statusField": { 107 | "enable": true, // 是否启用status字段 108 | "name": "status" // 字段的名字 109 | } 110 | } 111 | }, 112 | // swagger相关配置 113 | "swagger": { 114 | "enable": true, // 是否启用swagger文档功能 115 | "title": "nestfy-starter", // 文档的title 116 | "description": "The photo API description", // 文档的描述 117 | "schemas": "http", // 接口是否安全,仅支持(http 和 https两种) 118 | "version": "1.0", // 文档的版本 119 | "email": "qinyang_1980@qq.com", // 作者的联系email 120 | "path": "/doc" // 文档路由 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nestfy", 3 | "version": "0.7.9", 4 | "description": "基于Nestjs的后台框架", 5 | "author": "andy.qin ", 6 | "repository": { 7 | "url": "https://github.com/qinyang1980/nestfy" 8 | }, 9 | "license": "ISC", 10 | "scripts": { 11 | "tsc": "tsc", 12 | "start": "gulp start", 13 | "build": "gulp build", 14 | "lint": "gulp tslint", 15 | "start:prod": "gulp start:prod", 16 | "test": "gulp test", 17 | "test:watch": "gulp test:watch", 18 | "test:coverage": "gulp test:coverage", 19 | "e2e": "gulp e2e", 20 | "e2e:watch": "gulp e2e:watch" 21 | }, 22 | "dependencies": { 23 | "@nestjs/common": "^6.2.4", 24 | "@nestjs/core": "^6.2.4", 25 | "@nestjs/microservices": "^6.2.4", 26 | "@nestjs/platform-express": "^6.2.4", 27 | "@nestjs/swagger": "^3.0.2", 28 | "@nestjs/testing": "^6.2.4", 29 | "@nestjs/typeorm": "^6.1.1", 30 | "@nestjs/websockets": "^6.2.4", 31 | "body-parser": "^1.19.0", 32 | "class-transformer": "^0.2.3", 33 | "class-validator": "^0.9.1", 34 | "jsonc-parser": "^2.1.0", 35 | "jsonwebtoken": "^8.5.1", 36 | "log4js": "^4.3.1", 37 | "mkdirp": "0.5.1", 38 | "mysql": "^2.17.1", 39 | "node-schedule": "^1.3.2", 40 | "redis": "^2.8.0", 41 | "reflect-metadata": "^0.1.13", 42 | "request": "^2.88.0", 43 | "request-promise": "^4.2.4", 44 | "rxjs": "^6.5.2", 45 | "swagger-ui-express": "^4.0.5", 46 | "typeorm": "^0.2.17" 47 | }, 48 | "devDependencies": { 49 | "@types/body-parser": "^1.17.0", 50 | "@types/express": "^4.16.1", 51 | "@types/jest": "^24.0.13", 52 | "@types/mkdirp": "^0.5.2", 53 | "@types/node": "^12.0.3", 54 | "@types/node-schedule": "^1.2.3", 55 | "@types/supertest": "^2.0.7", 56 | "del": "^4.1.1", 57 | "fs-extra": "^8.0.1", 58 | "gulp": "3.9.1", 59 | "gulp-shell": "^0.7.0", 60 | "gulp-tslint": "^8.1.4", 61 | "jest": "^24.8.0", 62 | "supertest": "^4.0.2", 63 | "ts-jest": "^24.0.2", 64 | "ts-node": "^8.2.0", 65 | "tslint": "^5.16.0", 66 | "tslint-config-prettier": "^1.18.0", 67 | "typescript": "^3.4.5" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/lib/config/index.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as jsoncParser from 'jsonc-parser'; 3 | import * as path from 'path'; 4 | import { NESTFY } from '../constants'; 5 | import { IRootObject } from './interface'; 6 | 7 | const filePath = path.join(process.cwd(), `${NESTFY}.json`); 8 | const config: IRootObject = jsoncParser.parse((fs.readFileSync(filePath, 'utf8').toString())); 9 | 10 | export default config; 11 | -------------------------------------------------------------------------------- /src/lib/config/interface.ts: -------------------------------------------------------------------------------- 1 | import { CorsOptions } from '@nestjs/common/interfaces/external/cors-options.interface'; 2 | import { ValidatorOptions } from '@nestjs/common/interfaces/external/validator-options.interface'; 3 | import { Configuration } from 'log4js'; 4 | 5 | export interface ICors { 6 | enable: boolean; 7 | configuration: CorsOptions; 8 | } 9 | 10 | export interface IBodyParser { 11 | enable: boolean; 12 | limit: string; 13 | } 14 | 15 | export interface IValidation { 16 | enable: boolean; 17 | configuration: ValidatorOptions; 18 | } 19 | 20 | export interface ILog { 21 | enable: boolean; 22 | configuration: Configuration; 23 | } 24 | 25 | export interface IAuth { 26 | enable: boolean; 27 | headerTag: string; 28 | secret: string; 29 | expiresIn: string; 30 | decodedTag: string; 31 | } 32 | 33 | export interface IApp { 34 | port: number; 35 | setUpMsg: string; 36 | apiPrefix: IApiPrefix; 37 | } 38 | 39 | export interface IApiPrefix { 40 | enable: boolean; 41 | prefix: string; 42 | } 43 | 44 | export interface ISwagger { 45 | enable: boolean; 46 | title: string; 47 | description: string; 48 | schemas: string; 49 | version: string; 50 | email: string; 51 | path: string; 52 | } 53 | 54 | export interface IRequest { 55 | traceRequestDuration: boolean; 56 | cors: ICors; 57 | bodyParser: IBodyParser; 58 | validation: IValidation; 59 | auth: IAuth; 60 | } 61 | 62 | export interface IRootObject { 63 | app: IApp; 64 | logging: ILog; 65 | request: IRequest; 66 | response: IResponse; 67 | swagger: ISwagger; 68 | } 69 | 70 | export interface ISuccessField { 71 | enable: boolean; 72 | name: string; 73 | } 74 | 75 | export interface IStatusField { 76 | enable: boolean; 77 | name: string; 78 | } 79 | 80 | export interface ISuccess { 81 | defaultMessage: string; 82 | successField: ISuccessField; 83 | statusField: IStatusField; 84 | } 85 | 86 | export interface IFailure { 87 | defaultMessage: string; 88 | successField: ISuccessField; 89 | statusField: IStatusField; 90 | } 91 | 92 | export interface IResponse { 93 | success: ISuccess; 94 | failure: IFailure; 95 | } 96 | -------------------------------------------------------------------------------- /src/lib/constants.ts: -------------------------------------------------------------------------------- 1 | export const AUTH_SYMBOL = Symbol('Auth'); 2 | export const ROLES_SYMBOL = Symbol('Roles'); 3 | 4 | export const NESTFY = 'nestfy'; 5 | -------------------------------------------------------------------------------- /src/lib/decorators/auth.decorator.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from '@nestjs/common'; 2 | import { AUTH_SYMBOL } from '../constants'; 3 | 4 | /** 5 | * @author andy.qin 6 | * @param verifyToken 决定是否验证token 7 | */ 8 | export const Auth = (verifyToken: boolean) => SetMetadata(AUTH_SYMBOL, verifyToken); 9 | -------------------------------------------------------------------------------- /src/lib/decorators/config/symbols.ts: -------------------------------------------------------------------------------- 1 | export const TASK_SYMBOL = Symbol('Task'); 2 | -------------------------------------------------------------------------------- /src/lib/decorators/config/task.ts: -------------------------------------------------------------------------------- 1 | export interface ITaskConfig { 2 | /** 3 | * 定时任务的模式 4 | */ 5 | cron: string; 6 | /** 7 | * 是否打印日志,默认打印 8 | */ 9 | printLog?: boolean; 10 | /** 11 | * task的函数名 12 | */ 13 | taskName?: string; 14 | } 15 | -------------------------------------------------------------------------------- /src/lib/decorators/index.ts: -------------------------------------------------------------------------------- 1 | export * from './task.decorator'; 2 | export * from './auth.decorator'; 3 | -------------------------------------------------------------------------------- /src/lib/decorators/task.decorator.ts: -------------------------------------------------------------------------------- 1 | import * as schedule from 'node-schedule'; 2 | import { logger } from '../utils'; 3 | import { TASK_SYMBOL } from './config/symbols'; 4 | import { ITaskConfig } from './config/task'; 5 | 6 | /** 7 | * @author andy.qin 8 | * @description ScheduleTask装饰器 9 | * 替换被ScheduleTask修饰的函数,增加了对cron,日志,错误处理等的统一处理 10 | */ 11 | 12 | /* tslint:disable */ 13 | export function ScheduleTask(config?: ITaskConfig): Function { 14 | config = { 15 | cron: config.cron || '', 16 | taskName: config.taskName || '', 17 | printLog: config.printLog || true, 18 | }; 19 | 20 | return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => { 21 | const oldValue: Function = descriptor.value; 22 | config.taskName = propertyKey; 23 | 24 | // save meta data 25 | const existingTasks: any[] = Reflect.getOwnMetadata(TASK_SYMBOL, target) || []; 26 | existingTasks.push(config); 27 | Reflect.defineMetadata(TASK_SYMBOL, existingTasks, target); 28 | 29 | descriptor.value = async function(): Promise { 30 | try { 31 | if (config.printLog) { 32 | logger.info(`Task[${config.taskName}] start ******************** `); 33 | } 34 | await oldValue.apply(this, arguments); 35 | if (config.printLog) { 36 | logger.info(`Task[${config.taskName}] finish ******************** \n`); 37 | } 38 | } catch (err) { 39 | logger.error(err); 40 | } 41 | }; 42 | 43 | // define schedule 44 | const j = schedule.scheduleJob(config.cron, async () => { 45 | await descriptor.value.apply(this); 46 | }); 47 | 48 | return descriptor; 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /src/lib/filters/error.filter.ts: -------------------------------------------------------------------------------- 1 | import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from '@nestjs/common'; 2 | import * as express from 'express'; 3 | import { ResponseUtil } from '../utils'; 4 | 5 | @Catch(Error, HttpException) 6 | export class ErrorFilter implements ExceptionFilter { 7 | public catch(errorOrException: any, host: ArgumentsHost): void { 8 | const ctx = host.switchToHttp(); 9 | const request: express.Request = ctx.getRequest(); 10 | const response = ctx.getResponse(); 11 | 12 | let err = errorOrException; 13 | if (errorOrException instanceof HttpException) { 14 | err = handleHttpException(err); 15 | } 16 | 17 | const ret = ResponseUtil.err(request.url, err); 18 | response.status(ret.status).json(ret); 19 | } 20 | } 21 | 22 | function handleHttpException(exception: HttpException): Error { 23 | let err: any; 24 | const res = exception.getResponse(); 25 | 26 | if (typeof res === 'string') { 27 | err = new Error(res); 28 | } else if (typeof res === 'object') { 29 | err = new Error((res as any).message); 30 | } 31 | 32 | err.status = exception.getStatus(); 33 | err.errors = (exception as any).errors || undefined; 34 | return err; 35 | } 36 | -------------------------------------------------------------------------------- /src/lib/guards/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { CanActivate, ExecutionContext } from '@nestjs/common/interfaces'; 3 | import { Reflector } from '@nestjs/core'; 4 | import * as express from 'express'; 5 | import config from '../config'; 6 | import { AUTH_SYMBOL } from '../constants'; 7 | import { IVerifyTokenResult, TokenUtil } from '../utils/token.util'; 8 | 9 | /** 10 | * @description 通用的验证token的保护器 11 | */ 12 | @Injectable() 13 | export class AuthGuard implements CanActivate { 14 | constructor(private readonly reflector: Reflector) {} 15 | 16 | public canActivate(context: ExecutionContext): boolean { 17 | const req: express.Request = context.switchToHttp().getRequest(); 18 | const needVerifyToken = this.reflector.get(AUTH_SYMBOL, context.getHandler()); 19 | if (needVerifyToken === false) { 20 | return true; 21 | } 22 | 23 | const token = req.query.token || req.headers[config.request.auth.headerTag]; // 从query或者header中获取token 24 | const result: IVerifyTokenResult = TokenUtil.verifyToken(token); 25 | 26 | if (result.success) { 27 | req[config.request.auth.decodedTag] = result.decodedToken; 28 | return true; 29 | } else { 30 | // invalid 31 | throw result.error; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './decorators'; 2 | export * from './guards/auth.guard'; 3 | 4 | import { AppUtil, logger } from './utils'; 5 | export { AppUtil as NestfyApp, logger }; 6 | -------------------------------------------------------------------------------- /src/lib/interceptors/exception.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { CallHandler, ExecutionContext, HttpException, HttpStatus, Injectable, NestInterceptor } from '@nestjs/common'; 2 | import { Observable, throwError } from 'rxjs'; 3 | import { catchError } from 'rxjs/operators'; 4 | 5 | @Injectable() 6 | export class ErrorsInterceptor implements NestInterceptor { 7 | public intercept(context: ExecutionContext, next: CallHandler): Observable { 8 | return next.handle().pipe(catchError(err => throwError(new HttpException('New message', HttpStatus.BAD_GATEWAY)))); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/lib/interceptors/logging.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'; 2 | import { Observable } from 'rxjs'; 3 | import { tap } from 'rxjs/operators'; 4 | 5 | @Injectable() 6 | export class LoggingInterceptor implements NestInterceptor { 7 | public intercept(context: ExecutionContext, next: CallHandler): Observable { 8 | console.log('Before...'); 9 | 10 | const now = Date.now(); 11 | return next.handle().pipe(tap(() => console.log(`After... ${Date.now() - now}ms`))); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/lib/interceptors/timeout.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'; 2 | import { Observable } from 'rxjs'; 3 | import { timeout } from 'rxjs/operators'; 4 | 5 | @Injectable() 6 | export class TimeoutInterceptor implements NestInterceptor { 7 | public intercept(context: ExecutionContext, next: CallHandler): Observable { 8 | return next.handle().pipe(timeout(5000)); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/lib/interceptors/transform.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'; 2 | import { Observable } from 'rxjs'; 3 | import { map } from 'rxjs/operators'; 4 | import { ResponseUtil } from '../utils'; 5 | 6 | @Injectable() 7 | export class TransformInterceptor implements NestInterceptor { 8 | public intercept(context: ExecutionContext, next: CallHandler): Observable { 9 | return next.handle().pipe(map(data => ResponseUtil.ok(data))); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/lib/middlewares/request-log.middleware.ts: -------------------------------------------------------------------------------- 1 | import { logger } from '../utils/log.util'; 2 | 3 | /** 4 | * @author andy.qin 5 | * 记录请求时间和处理时长的日志中间件 6 | */ 7 | export const requestLogMiddleware = (req, res, next) => { 8 | const t = new Date(); 9 | logger.debug(`>>>>>>>> Started ${t.toLocaleString()} ${req.method} ${req.url} ${req.ip}`); 10 | 11 | res.on('finish', () => { 12 | const n = new Date(); 13 | const duration = n.getTime() - t.getTime(); 14 | 15 | logger.debug(`<<<<<<<< Completed ${res.statusCode} (${duration}ms)\n\n`); 16 | }); 17 | 18 | next(); 19 | }; 20 | -------------------------------------------------------------------------------- /src/lib/middlewares/verify-token.middleware.ts: -------------------------------------------------------------------------------- 1 | import config from '../config'; 2 | import { ResponseUtil } from '../utils'; 3 | import { IVerifyTokenResult, TokenUtil } from '../utils/token.util'; 4 | 5 | /** 6 | * @author andy.qin 7 | * 校验token的中间件 8 | */ 9 | export const verifyTokenMiddleware = (req, res, next) => { 10 | const token = req.query.token || req.headers[config.request.auth.headerTag]; // 从query或者header中获取token 11 | const result: IVerifyTokenResult = TokenUtil.verifyToken(token); 12 | 13 | if (result.success) { 14 | req.decodedToken = result.decodedToken; 15 | next(); 16 | } else { 17 | // invalid 18 | res.status(result.error.httpCode).json(ResponseUtil.err(req.url, result.error)); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /src/lib/pipes/index.ts: -------------------------------------------------------------------------------- 1 | export * from './validation.pipe'; 2 | -------------------------------------------------------------------------------- /src/lib/pipes/validation.pipe.ts: -------------------------------------------------------------------------------- 1 | import { BadRequestException, Injectable } from '@nestjs/common'; 2 | import { ArgumentMetadata, PipeTransform } from '@nestjs/common'; 3 | import { plainToClass } from 'class-transformer'; 4 | import { validate, ValidatorOptions } from 'class-validator'; 5 | 6 | @Injectable() 7 | export class ValidationPipe implements PipeTransform { 8 | private options: ValidatorOptions; 9 | public constructor(validatorOptions: ValidatorOptions) { 10 | this.options = validatorOptions; 11 | } 12 | 13 | public async transform(value: any, metadata: ArgumentMetadata): Promise { 14 | const { metatype } = metadata; 15 | if (!metatype || !this.toValidate(metatype)) { 16 | return value; 17 | } 18 | const object = plainToClass(metatype, value); 19 | const errors = await validate(object, this.options); 20 | 21 | if (errors.length <= 0) { 22 | return object; // 这里需要返回对象 23 | } 24 | 25 | const err = new BadRequestException( 26 | `You have an error in your request's body. Check 'errors' field for more details!`, 27 | ); 28 | (err as any).errors = errors; 29 | 30 | throw err; 31 | } 32 | 33 | private toValidate(metatype: any): boolean { 34 | const types = [String, Boolean, Number, Array, Object]; 35 | return !types.find(type => metatype === type); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/lib/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./../tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "./" 5 | }, 6 | "include": ["*.ts", "**/*.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /src/lib/utils/app.util.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory, Reflector } from '@nestjs/core'; 2 | import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; 3 | import * as bodyParser from 'body-parser'; 4 | import { configure } from 'log4js'; 5 | import config from '../config'; 6 | import { ErrorFilter } from '../filters/error.filter'; 7 | import { AuthGuard } from '../guards/auth.guard'; 8 | import { TransformInterceptor } from '../interceptors/transform.interceptor'; 9 | import { requestLogMiddleware } from '../middlewares/request-log.middleware'; 10 | import { ValidationPipe } from '../pipes'; 11 | import { logger } from './log.util'; 12 | 13 | /* tslint:disable */ 14 | export class AppUtil { 15 | public static async bootstrap(appModule: any): Promise { 16 | const app = await NestFactory.create(appModule); 17 | 18 | if (config.request.bodyParser.enable) { 19 | app.use(bodyParser.json({ limit: config.request.bodyParser.limit })); 20 | app.use(bodyParser.urlencoded({ limit: config.request.bodyParser.limit, extended: false })); 21 | } 22 | 23 | // 跨域支持 24 | if (config.request.cors.enable) { 25 | app.enableCors(config.request.cors.configuration); 26 | } 27 | 28 | // 是否打印请求 29 | if (config.request.traceRequestDuration) { 30 | app.use(requestLogMiddleware); 31 | } 32 | 33 | // 日志配置 34 | if (config.logging.enable) { 35 | configure(config.logging.configuration); 36 | } 37 | 38 | // 校验Request 39 | if (config.request.validation.enable) { 40 | app.useGlobalPipes( 41 | new ValidationPipe(config.request.validation.configuration), 42 | ); 43 | } 44 | 45 | // 路由前缀 46 | if (config.app.apiPrefix.enable) { 47 | app.setGlobalPrefix(config.app.apiPrefix.prefix); 48 | } 49 | 50 | // token校验 51 | if (config.request.auth.enable) { 52 | app.useGlobalGuards(new AuthGuard(new Reflector())); 53 | } 54 | 55 | // 统一错误处理 56 | app.useGlobalFilters(new ErrorFilter()); 57 | 58 | // 统一response格式化处理 59 | app.useGlobalInterceptors(new TransformInterceptor()); 60 | 61 | const schemas = 62 | config.swagger.schemas === 'http' || config.swagger.schemas === 'https' ? config.swagger.schemas : 'http'; 63 | 64 | if (config.swagger.enable) { 65 | const options = new DocumentBuilder() 66 | .setTitle(config.swagger.title) 67 | .setBasePath(config.app.apiPrefix.prefix) 68 | .setDescription(config.swagger.description) 69 | .setVersion(config.swagger.version) 70 | .setSchemes(schemas) 71 | .setContactEmail(config.swagger.email) 72 | .build(); 73 | const document = SwaggerModule.createDocument(app, options); 74 | SwaggerModule.setup(config.swagger.path, app, document); 75 | } 76 | 77 | app.listen(config.app.port, () => { 78 | logger.info(config.app.setUpMsg); 79 | }); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/lib/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './log.util'; 2 | export * from './response.util'; 3 | export * from './token.util'; 4 | export * from './app.util'; 5 | -------------------------------------------------------------------------------- /src/lib/utils/log.util.ts: -------------------------------------------------------------------------------- 1 | import { getLogger } from 'log4js'; 2 | 3 | const logger = getLogger(); 4 | export { logger }; 5 | -------------------------------------------------------------------------------- /src/lib/utils/response.util.ts: -------------------------------------------------------------------------------- 1 | import config from '../config'; 2 | 3 | /** 4 | * @author andy.qin 5 | * @description 统一定义返回结果的格式 6 | */ 7 | export interface ICustomResponse { 8 | /** 9 | * 时间戳 10 | */ 11 | timestamp?: Date; 12 | /** 13 | * 请求是否成功 14 | */ 15 | success?: boolean; 16 | /** 17 | * HTTP状态码(200,400,404,500) 18 | */ 19 | status?: number; 20 | /** 21 | * 成功才返回的查询数据 22 | */ 23 | data?: object | object[]; 24 | /** 25 | * 错误才返回的错误描述 26 | */ 27 | errors?: any; 28 | /** 29 | * 整体描述 30 | */ 31 | message?: string; 32 | /** 33 | * HTTP请求路径 34 | */ 35 | path?: string; 36 | /** 37 | * 扩展属性 38 | */ 39 | [propName: string]: any; 40 | } 41 | 42 | function failedJson(path: string, error: any): ICustomResponse { 43 | const result: ICustomResponse = {}; 44 | if (config.response.failure.successField.enable) { 45 | result[config.response.failure.successField.name] = error.status === 200; 46 | } 47 | if (config.response.failure.statusField.enable) { 48 | result[config.response.failure.statusField.name] = error.status || 500; 49 | } 50 | 51 | const fixedFields = { 52 | timestamp: new Date(), 53 | message: error.message || config.response.failure.defaultMessage, 54 | errors: error.errors || undefined, 55 | path: path || undefined, 56 | }; 57 | 58 | return { ...result, ...fixedFields, ...error }; 59 | } 60 | 61 | function successJson(message: string, data: object | object[]): any { 62 | const result: ICustomResponse = {}; 63 | if (config.response.failure.successField.enable) { 64 | result[config.response.failure.successField.name] = true; 65 | } 66 | if (config.response.failure.statusField.enable) { 67 | result[config.response.failure.statusField.name] = 200; 68 | } 69 | const fixedFields = { 70 | message: message || null, 71 | data: data || {}, 72 | }; 73 | 74 | return { ...result, ...fixedFields }; 75 | } 76 | 77 | function formatFindAndCount(content: any): any { 78 | // findAndCount 类型的结果 79 | if (Array.isArray(content) && content.length === 2) { 80 | const [a, b] = content; 81 | if (Array.isArray(a) && Number.isInteger(b)) { 82 | content = { rows: a, count: b }; 83 | } 84 | } 85 | return content; 86 | } 87 | 88 | export class ResponseUtil { 89 | public static ok(data: any): ICustomResponse { 90 | return successJson(config.response.success.defaultMessage, formatFindAndCount(data)); 91 | } 92 | 93 | public static err(path: string, error: any): ICustomResponse { 94 | return failedJson(path, error); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/lib/utils/token.util.ts: -------------------------------------------------------------------------------- 1 | import { BadRequestException } from '@nestjs/common'; 2 | import * as jwt from 'jsonwebtoken'; 3 | import config from '../config'; 4 | import { logger } from '../utils/log.util'; 5 | 6 | export interface IVerifyTokenResult { 7 | /** 8 | * token 校验是否成功 9 | */ 10 | success: boolean; 11 | /** 12 | * 解码后的token对象 13 | */ 14 | decodedToken?: any; 15 | /** 16 | * 校验失败的错误 17 | */ 18 | error?: any; 19 | } 20 | 21 | /** 22 | * @author andy.qin 23 | * 产生token,验证token 24 | */ 25 | export class TokenUtil { 26 | public static genToken(payload: any): string { 27 | const plainObj = Object.assign({}, payload); 28 | const token = jwt.sign(plainObj, config.request.auth.secret, { expiresIn: config.request.auth.expiresIn }); 29 | logger.info(`genToken: ${token}`); 30 | return token; 31 | } 32 | 33 | public static verifyToken(token: string): IVerifyTokenResult { 34 | try { 35 | const decoded = jwt.verify(token, config.request.auth.secret); 36 | logger.debug('verifyToken - decoded info: %o', decoded); 37 | return { success: true, decodedToken: decoded }; 38 | } catch (err) { 39 | logger.error(err); 40 | 41 | let retError = new BadRequestException('token验证失败'); 42 | if (err.name === 'JsonWebTokenError' && err.message === 'jwt must be provided') { 43 | retError = new BadRequestException('必须提供token'); 44 | } else if (err.name === 'TokenExpiredError' && err.message === 'jwt expired') { 45 | retError = new BadRequestException('token超时错误'); 46 | } 47 | 48 | return { success: false, error: retError }; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/testing/common/config/config.dev.ts: -------------------------------------------------------------------------------- 1 | export const configDevelopment = { 2 | debug: true, 3 | 4 | // 环境配置 5 | host: 'localhost', 6 | apiURL: 'http://localhost:3000', 7 | 8 | mysql: { 9 | type: 'mysql', 10 | host: 'localhost', 11 | port: 3306, 12 | username: 'root', 13 | password: 'root', 14 | database: 'test', 15 | entities: [__dirname + '/**/*.entity{.ts,.js}'], 16 | synchronize: true, 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /src/testing/common/config/config.prod.ts: -------------------------------------------------------------------------------- 1 | export const configProduction = { 2 | debug: false, 3 | 4 | // 环境配置 5 | host: 'xxx.com.cn', 6 | apiURL: 'http://xxx.com.cn:3000', 7 | 8 | mysql: { 9 | type: 'mysql', 10 | host: 'localhost', 11 | port: 3306, 12 | username: 'root', 13 | password: 'root', 14 | database: 'test', 15 | entities: [__dirname + '/**/*.entity{.ts,.js}'], 16 | synchronize: true, 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /src/testing/common/config/errors.ts: -------------------------------------------------------------------------------- 1 | import { InternalServerErrorException } from '@nestjs/common'; 2 | 3 | /** 4 | * @author hai.zhang 5 | * @param code 6 | * @description 错误码配置 7 | */ 8 | const errors = { 9 | // HTTP 10 | '200': 'OK', 11 | 12 | // -1000 ~ -1200 13 | '-1000': '数据库连接失败!', 14 | '-1001': 'SQL语法错误!', 15 | '-1002': '数据库 - 无法查找到数据!', 16 | '-1003': '数据库 - ID重复', 17 | '-1004': '数据库 - 手机号码重复', 18 | '-1005': '输入的ID无效,查询不到相关数据!', 19 | 20 | // -1300 ~ -1400 21 | '-1300': '签名不正确!', 22 | '-1301': '此订单已经处理过!', 23 | '-1302': 'optional参数错误!', 24 | '-1303': '订单的金额不符!', 25 | '-1304': '必须提供token', 26 | '-1305': 'token验证失败', 27 | '-1306': 'token超时错误', 28 | '-1307': '优惠券金额不足', 29 | '-1308': '优惠券次数不足', 30 | '-1309': '优惠券还不可以使用', 31 | '-1310': '优惠券已过期', 32 | '-1311': 'transactionId错误', 33 | '-1312': 'optional.flag错误', 34 | 35 | // -1500 ~ -1600 36 | '-1500': '账号不存在', 37 | '-1501': '密码与账号不符', 38 | '-1502': '手机号或验证码有误', 39 | '-1503': '账号已存在', 40 | '-1504': '手机验证码校验失败,验证码错误!', 41 | '-1505': '验证码校验失败,验证码超时!', 42 | '-1506': '图片解析失败', 43 | '-1507': '密码错误!', 44 | '-1508': '无法获取用户对应的角色信息!用户信息创建有误!', 45 | '-1509': '检测出你属于平台用户,但是移动平台不支持你的系统角色!', 46 | '-1510': '该用户已经登陆过(绑定成功),无需再次绑定!', 47 | '-1511': '参数有误', 48 | '-1512': '医生不存在', 49 | '-1513': '患者不存在', 50 | '-1514': '角色不存在', 51 | '-1515': '角色不符', // 患者不能登录pc端系统 52 | '-1516': '模板命名重复,请重新命名', // 模板命名重复 53 | '-1517': '问诊不存在或状态错误', // 问诊状态或问诊ID错误 54 | '-1518': '手机号已占用', 55 | '-1519': '该平台已占用', 56 | 57 | // -1700 ~ -1800 58 | '-1700': '同步错误 - 无法识别传入的资源类型!', 59 | '-1701': '不可提交', 60 | '-1702': '传参错误', 61 | '-1703': '没有病人在排队', 62 | '-1704': 'QueryInfo参数错误', 63 | '-1705': '专家离线中,不可进入诊室', 64 | 65 | '-2000': '订单无效,请发起新问诊', 66 | '-2001': '订单状态有误', 67 | '-2002': '订单失效', 68 | 69 | // -5000 70 | '-5000': '未知错误', 71 | }; 72 | 73 | export const error = { 74 | error(code: any): Error { 75 | code = `${code}`; 76 | if (!Reflect.has(errors, code)) { 77 | code = '-5000'; 78 | } 79 | const msg = errors[code]; 80 | const ise = new InternalServerErrorException(msg); 81 | (ise as any).errno = code; 82 | return ise; 83 | }, 84 | }; 85 | -------------------------------------------------------------------------------- /src/testing/common/config/index.ts: -------------------------------------------------------------------------------- 1 | import { configDevelopment } from './config.dev'; 2 | import { configProduction } from './config.prod'; 3 | import { error } from './errors'; 4 | import { commonSetting } from './setting'; 5 | 6 | const DEV = 'development'; 7 | let env = process.env.NODE_ENV || DEV; 8 | env = env.toLowerCase(); 9 | 10 | const getConfig = () => { 11 | return env === DEV ? configDevelopment : configProduction; 12 | }; 13 | 14 | // 将setting跟config合并 15 | const config = Object.assign({}, getConfig(), commonSetting, error); 16 | 17 | export default config; 18 | -------------------------------------------------------------------------------- /src/testing/common/config/setting.ts: -------------------------------------------------------------------------------- 1 | export const commonSetting = {}; 2 | -------------------------------------------------------------------------------- /src/testing/modules/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { ConnectionOptions } from 'typeorm'; 4 | import config from '../common/config'; 5 | import { PhotoModule } from './photo/photo.module'; 6 | 7 | @Module({ 8 | // imports: [TypeOrmModule.forRoot(config.mysql as ConnectionOptions), PhotoModule] 9 | imports: [ 10 | TypeOrmModule.forRoot({ 11 | type: 'mysql', 12 | host: 'localhost', 13 | port: 3306, 14 | username: 'root', 15 | password: 'root', 16 | database: 'test', 17 | entities: [__dirname + '/**/*.entity{.ts,.js}'], 18 | synchronize: true, 19 | }), 20 | PhotoModule, 21 | ], 22 | }) 23 | export class ApplicationModule {} 24 | -------------------------------------------------------------------------------- /src/testing/modules/photo/dto/create-photo.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiModelProperty } from '@nestjs/swagger'; 2 | import { IsBoolean, IsDefined, IsInt, IsString } from 'class-validator'; 3 | 4 | export class CreatePhotoDto { 5 | @ApiModelProperty() 6 | @IsDefined() 7 | @IsString() 8 | public name: string; 9 | 10 | @ApiModelProperty() 11 | @IsDefined() 12 | @IsString() 13 | public description: string; 14 | 15 | @IsString() 16 | @ApiModelProperty() 17 | public filename: string; 18 | 19 | @IsInt() 20 | @ApiModelProperty() 21 | public views: number; 22 | 23 | @IsBoolean() 24 | @ApiModelProperty() 25 | public isPublished: boolean; 26 | } 27 | -------------------------------------------------------------------------------- /src/testing/modules/photo/dto/query-photos.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiModelProperty } from '@nestjs/swagger'; 2 | import { Type } from 'class-transformer'; 3 | import { IsDefined, IsInt, IsNotEmpty, IsNumberString, IsString } from 'class-validator'; 4 | 5 | export class QueryPhotosDto { 6 | @ApiModelProperty({ description: '查询偏移量', required: true }) 7 | @IsDefined() 8 | @Type(() => Number) 9 | @IsInt() 10 | public offset: number; 11 | 12 | @ApiModelProperty({ description: '查询个数', required: true }) 13 | @IsDefined() 14 | @Type(() => Number) 15 | @IsInt() 16 | public limit: number; 17 | 18 | @ApiModelProperty({ description: '名称', required: false }) 19 | @IsString() 20 | @IsNotEmpty() 21 | public readonly name: string; 22 | } 23 | -------------------------------------------------------------------------------- /src/testing/modules/photo/photo.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test } from '@nestjs/testing'; 2 | import { Repository } from 'typeorm'; 3 | import { PhotoController } from './photo.controller'; 4 | import { Photo } from './photo.entity'; 5 | import { PhotoService } from './photo.service'; 6 | 7 | describe('PhotoController', () => { 8 | let photoController: PhotoController; 9 | let photoService: PhotoService; 10 | 11 | beforeEach(async () => { 12 | photoService = new PhotoService(new Repository()); 13 | photoController = new PhotoController(photoService); 14 | }); 15 | 16 | describe('findAll', () => { 17 | it('should return an array of photos', async () => { 18 | const result = ['test']; 19 | jest.spyOn(photoService, 'findAll').mockImplementation(() => result); 20 | 21 | const ret = await photoController.findAll(); 22 | expect(ret).toBe(result); 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/testing/modules/photo/photo.controller.ts: -------------------------------------------------------------------------------- 1 | import { ForbiddenException, ParseIntPipe, Query, UseGuards } from '@nestjs/common'; 2 | import { Body, Controller, Delete, Get, Param, Post, Put } from '@nestjs/common'; 3 | import { ApiUseTags } from '@nestjs/swagger'; 4 | import * as rp from 'request-promise'; 5 | import { Auth, logger } from '../../../lib'; 6 | import config from '../../common/config'; 7 | import { CreatePhotoDto } from './dto/create-photo.dto'; 8 | import { QueryPhotosDto } from './dto/query-photos.dto'; 9 | import { Photo } from './photo.entity'; 10 | import { PhotoService } from './photo.service'; 11 | 12 | @ApiUseTags('photos') 13 | @Controller('v1/photos') 14 | export class PhotoController { 15 | constructor(private readonly _photoService: PhotoService) {} 16 | 17 | @Post() 18 | public async create(@Body() createPhotoDto: CreatePhotoDto): Promise { 19 | return this._photoService.create(createPhotoDto as Photo); 20 | } 21 | 22 | @Get() 23 | public async findAllAndCount(@Query() query: QueryPhotosDto): Promise { 24 | logger.info(query); 25 | return this._photoService.findAllAndCount(); 26 | } 27 | 28 | @Get('findAll') 29 | public async findAll(): Promise { 30 | return this._photoService.findAll(); 31 | } 32 | 33 | @Get(':id') 34 | @Auth(false) 35 | public findOne(@Param('id', new ParseIntPipe()) id: number): Promise { 36 | return this._photoService.findOneById(id); 37 | } 38 | 39 | @Put(':id') 40 | public async modify(@Param('id', new ParseIntPipe()) id: number, @Body() photo: CreatePhotoDto): Promise { 41 | return this._photoService.modify(id, photo as Photo); 42 | } 43 | 44 | @Delete(':id') 45 | public async remove(@Param('id', new ParseIntPipe()) id: number): Promise { 46 | return this._photoService.remove(id); 47 | } 48 | 49 | // 转发测试 50 | @Get('transfer/test') 51 | @Auth(false) 52 | public async transfer(): Promise { 53 | const options = { 54 | uri: 'https://jsonplaceholder.typicode.com/todos', 55 | json: true, 56 | }; 57 | 58 | const ret = await rp(options); 59 | return ret; 60 | } 61 | 62 | // 异常测试 63 | @Get('exception/testing') 64 | public async exceptionTesting(): Promise { 65 | throw new Error('test'); 66 | } 67 | 68 | @Get('exception/normal-error') 69 | public async testNormalError(): Promise { 70 | throw new Error('test'); 71 | } 72 | 73 | @Get('exception/special-error') 74 | public async testForbiddenError(): Promise { 75 | throw new ForbiddenException('forbidden'); 76 | } 77 | 78 | @Get('exception/config-error') 79 | public async testConfigError(): Promise { 80 | throw config.error(-1300); 81 | } 82 | 83 | @Get('exception/programming-error') 84 | public async testProgrammingError(): Promise { 85 | return (this as any).func2222(); 86 | } 87 | 88 | @Get('exception/database-error') 89 | public async testDatabaseError(): Promise { 90 | return this._photoService.dbExceptionTest(); 91 | } 92 | 93 | @Post('exception/validation-error') 94 | public async testValidationError(@Body() createPhotoDto: CreatePhotoDto): Promise { 95 | return 3; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/testing/modules/photo/photo.entity.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; 2 | 3 | @Entity() 4 | export class Photo { 5 | @PrimaryGeneratedColumn() 6 | public id: number; 7 | 8 | @Column({ length: 500 }) 9 | public name: string; 10 | 11 | @Column('text') 12 | public description: string; 13 | 14 | @Column() 15 | public filename: string; 16 | 17 | @Column('int') 18 | public views: number; 19 | 20 | @Column() 21 | public isPublished: boolean; 22 | } 23 | -------------------------------------------------------------------------------- /src/testing/modules/photo/photo.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { PhotoController } from './photo.controller'; 4 | import { Photo } from './photo.entity'; 5 | import { PhotoService } from './photo.service'; 6 | 7 | @Module({ 8 | imports: [TypeOrmModule.forFeature([Photo])], 9 | providers: [PhotoService], 10 | controllers: [PhotoController], 11 | }) 12 | export class PhotoModule {} 13 | -------------------------------------------------------------------------------- /src/testing/modules/photo/photo.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { Repository } from 'typeorm'; 4 | import { logger, ScheduleTask } from '../../../lib'; 5 | import { Photo } from './photo.entity'; 6 | 7 | @Injectable() 8 | export class PhotoService { 9 | constructor( 10 | @InjectRepository(Photo) 11 | private readonly _photoRepository: Repository, 12 | ) {} 13 | 14 | /** 15 | * 获取所有photos记录,并且返回总数 16 | */ 17 | public findAllAndCount(): Promise<[Photo[], number]> { 18 | return this._photoRepository.findAndCount(); 19 | } 20 | 21 | /** 22 | * 获取所有photos记录 23 | */ 24 | public findAll(): Promise { 25 | return this._photoRepository.find(); 26 | } 27 | 28 | /** 29 | * 根据id获取单个photo 30 | */ 31 | public async findOneById(id: number): Promise { 32 | return this._photoRepository.findOne(id); 33 | } 34 | 35 | /** 36 | * 根据photo对象创建一个新的记录 37 | */ 38 | public async create(photo: Photo): Promise { 39 | return this._photoRepository.save(photo); 40 | } 41 | 42 | /** 43 | * 根据id,修改某条记录 44 | */ 45 | public async modify(id: number, photo: Photo): Promise { 46 | photo.id = id; 47 | return this._photoRepository.save(photo); 48 | } 49 | 50 | /** 51 | * 删除某条记录 52 | */ 53 | public async remove(id: number): Promise { 54 | const photo: Photo = {} as Photo; 55 | photo.id = id; 56 | return this._photoRepository.remove(photo); 57 | } 58 | 59 | /** 60 | * 数据库异常测试 61 | */ 62 | public async dbExceptionTest(): Promise { 63 | const id: number = 3; 64 | return this._photoRepository 65 | .createQueryBuilder('photo') 66 | .where('photo.id2 = :id', { id }) 67 | .getMany(); 68 | } 69 | 70 | @ScheduleTask({ cron: '*/1 * * * *' }) 71 | private testTask(): void { 72 | logger.info('测试定期任务...'); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/testing/server.ts: -------------------------------------------------------------------------------- 1 | import { NestfyApp } from '../lib'; 2 | import { ApplicationModule } from './modules/app.module'; 3 | 4 | NestfyApp.bootstrap(ApplicationModule); 5 | -------------------------------------------------------------------------------- /src/testing/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./../tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "./" 5 | }, 6 | "include": ["*.ts", "**/*.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /src/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "noImplicitAny": false, 6 | "noUnusedLocals": false, 7 | "removeComments": false, 8 | "noLib": false, 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es2017", 12 | "sourceMap": false, 13 | "allowJs": false, 14 | "strict": true, 15 | "strictNullChecks": false 16 | }, 17 | "exclude": ["../node_modules", "./**/*.spec.ts"] 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "noImplicitAny": false, 7 | "removeComments": true, 8 | "noLib": false, 9 | "outDir": "./build", 10 | "sourceMap": true, 11 | "moduleResolution": "node", 12 | "experimentalDecorators": true, 13 | "emitDecoratorMetadata": true 14 | }, 15 | "include": ["src/**/*"], 16 | "exclude": ["node_modules", "build", "dist", "**/*.spec.ts"] 17 | } 18 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:recommended", "tslint-config-prettier"], 3 | "rules": { 4 | "adjacent-overload-signatures": true, 5 | "object-literal-sort-keys": false, 6 | "member-access": true, 7 | "no-string-literal": false, 8 | "member-ordering": [ 9 | true, 10 | { 11 | "order": [ 12 | "public-static-field", 13 | "protected-static-field", 14 | "private-static-field", 15 | "public-instance-field", 16 | "protected-instance-field", 17 | "private-instance-field", 18 | "constructor", 19 | "public-static-method", 20 | "protected-static-method", 21 | "private-static-method", 22 | "public-instance-method", 23 | "protected-instance-method", 24 | "private-instance-method" 25 | ] 26 | } 27 | ], 28 | "no-internal-module": true, 29 | "unified-signatures": true, 30 | "typedef": [true, "call-signature", "parameter", "member-variable-declaration"], 31 | "typedef-whitespace": [ 32 | true, 33 | { 34 | "call-signature": "nospace", 35 | "index-signature": "nospace", 36 | "parameter": "nospace", 37 | "property-declaration": "nospace", 38 | "variable-declaration": "nospace" 39 | }, 40 | { 41 | "call-signature": "onespace", 42 | "index-signature": "onespace", 43 | "parameter": "onespace", 44 | "property-declaration": "onespace", 45 | "variable-declaration": "onespace" 46 | } 47 | ], 48 | "curly": true, 49 | "label-position": true, 50 | "no-arg": true, 51 | "no-any": false, 52 | "no-conditional-assignment": true, 53 | "no-duplicate-variable": true, 54 | "no-empty": true, 55 | "no-eval": true, 56 | "no-namespace": true, 57 | "no-reference": false, 58 | "no-var-requires": true, 59 | "no-empty-interface": false, 60 | "no-switch-case-fall-through": true, 61 | "no-unused-expression": true, 62 | "no-var-keyword": true, 63 | "no-debugger": true, 64 | "no-unsafe-finally": true, 65 | "no-use-before-declare": false, 66 | "radix": true, 67 | "switch-default": true, 68 | "triple-equals": [true, "allow-null-check", "allow-undefined-check"], 69 | "use-isnan": true, 70 | "eofline": true, 71 | "max-classes-per-file": [true, 2], 72 | "cyclomatic-complexity": [true, 8], 73 | "indent": [true, "spaces"], 74 | "max-line-length": [true, 120], 75 | "no-trailing-whitespace": true, 76 | "interface-name": [true, "always-prefix"], 77 | "trailing-comma": [true, { "multiline": "always", "singleline": "never" }], 78 | "align": [true, "arguments", "parameters"], 79 | "class-name": true, 80 | "comment-format": [true, "check-space"], 81 | "jsdoc-format": true, 82 | "new-parens": true, 83 | "no-console": false, 84 | "no-angle-bracket-type-assertion": true, 85 | "no-consecutive-blank-lines": [true], 86 | "one-line": [true, "check-catch", "check-finally", "check-else", "check-open-brace", "check-whitespace"], 87 | "one-variable-per-declaration": [true], 88 | "quotemark": [true, "single", "jsx-double", "avoid-escape"], 89 | "semicolon": [true, "always"], 90 | "variable-name": [true, "ban-keywords", "check-format", "allow-leading-underscore", "allow-pascal-case"], 91 | "whitespace": [ 92 | true, 93 | "check-branch", 94 | "check-decl", 95 | "check-operator", 96 | "check-module", 97 | "check-separator", 98 | "check-type", 99 | "check-typecast" 100 | ] 101 | } 102 | } 103 | --------------------------------------------------------------------------------