├── .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 |
--------------------------------------------------------------------------------