├── .gitignore
├── .prettierrc
├── README.md
├── nodemon.json
├── package.json
├── src
├── config
│ ├── base.config.ts
│ ├── dev.config.ts
│ ├── local.config.ts
│ ├── main.config.ts
│ └── winston.config.ts
├── constants
│ ├── cache.constant.ts
│ ├── error
│ │ ├── general.constant.ts
│ │ └── recharge.constant.ts
│ ├── meta.constant.ts
│ ├── system.constant.ts
│ └── text.constant.ts
├── decorators
│ ├── cache.decorator.ts
│ ├── inject_service.decorator.ts
│ └── response.decorator.ts
├── exceptions
│ ├── bad_request.error.ts
│ ├── forbidden.error.ts
│ ├── platform.error.ts
│ ├── unauthorized.error.ts
│ └── validation.error.ts
├── filters
│ └── exception.filter.ts
├── interceptors
│ ├── cache.interceptor.ts
│ ├── error.interceptor.ts
│ ├── logging.interceptor.ts
│ └── transform.interceptor.ts
├── interfaces
│ ├── config.interface.ts
│ └── response.interface.ts
├── middlewares
│ ├── cors.middleware.ts
│ └── origin.middleware.ts
├── models
│ ├── anothor_lifeblood.model.ts
│ ├── assist.model.ts
│ ├── lifeblood.model.ts
│ ├── monster.model.ts
│ ├── naked.model.ts
│ ├── strengthen.model.ts
│ └── undisguised.model.ts
├── modules
│ ├── account
│ │ └── recharge
│ │ │ ├── recharge.controller.ts
│ │ │ ├── recharge.dto.ts
│ │ │ ├── recharge.module.ts
│ │ │ └── recharge.service.ts
│ ├── app
│ │ ├── app.controller.ts
│ │ └── app.module.ts
│ └── authorize
│ │ ├── authorize.controller.ts
│ │ ├── authorize.dto.ts
│ │ └── authorize.module.ts
├── pipes
│ └── validation.pipe.ts
├── processors
│ ├── base
│ │ └── base.service.ts
│ ├── cache
│ │ ├── cache.module.ts
│ │ ├── cache.service.config.ts
│ │ └── cache.service.ts
│ └── helper
│ │ ├── crypto.serivce.ts
│ │ ├── crypto.spec.ts
│ │ └── helper.module.ts
└── server.ts
├── tsconfig.build.json
├── tsconfig.json
├── tslint.json
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | .vscode
4 | .git2
5 | .git3
6 | logs
7 | # compiled output
8 | /dist
9 | /tmp
10 | /out-tsc
11 | # Only exists if Bazel was run
12 | /bazel-out
13 |
14 | # dependencies
15 | /node_modules
16 |
17 | # profiling files
18 | chrome-profiler-events.json
19 | speed-measure-plugin.json
20 |
21 | # IDEs and editors
22 | /.idea
23 | .project
24 | .classpath
25 | .c9/
26 | *.launch
27 | .settings/
28 | *.sublime-workspace
29 |
30 | # IDE - VSCode
31 | .vscode/*
32 | !.vscode/settings.json
33 | !.vscode/tasks.json
34 | !.vscode/launch.json
35 | !.vscode/extensions.json
36 | .history/*
37 |
38 | # misc
39 | /connect.lock
40 | /coverage
41 | /libpeerconnection.log
42 | npm-debug.log
43 | yarn-error.log
44 | testem.log
45 | /typings
46 |
47 | # System Files
48 | .DS_Store
49 | Thumbs.db
50 |
51 | # Clinic
52 | .clinic/
53 |
54 | # Temporary files
55 | .tmp/
56 |
57 | # Environment files
58 | .env
59 | .env.test
60 | .env.local
61 | .env.development
62 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "bracketSpacing": true,
3 | "semi": false,
4 | "singleQuote": true,
5 | "printWidth": 100
6 | }
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | [travis-image]: https://api.travis-ci.org/nestjs/nest.svg?branch=master
6 | [travis-url]: https://travis-ci.org/nestjs/nest
7 | [linux-image]: https://img.shields.io/travis/nestjs/nest/master.svg?label=linux
8 | [linux-url]: https://travis-ci.org/nestjs/nest
9 |
10 | A progressive Node.js framework for building efficient and scalable server-side applications, heavily inspired by Angular.
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
26 |
27 | ## Description
28 |
29 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
30 |
31 | ## Installation
32 |
33 | ```bash
34 | $ npm install
35 | ```
36 |
37 | ## Running the app
38 |
39 | ```bash
40 | # development
41 | $ npm run start
42 |
43 | # watch mode
44 | $ npm run start:dev
45 |
46 | # production mode
47 | $ npm run start:prod
48 | ```
49 |
50 | ## Test
51 |
52 | ```bash
53 | # unit tests
54 | $ npm run test
55 |
56 | # e2e tests
57 | $ npm run test:e2e
58 |
59 | # test coverage
60 | $ npm run test:cov
61 | ```
62 |
63 | ## Support
64 |
65 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
66 |
67 | ## Stay in touch
68 |
69 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
70 | - Website - [https://nestjs.com](https://nestjs.com/)
71 | - Twitter - [@nestframework](https://twitter.com/nestframework)
72 |
73 | ## License
74 |
75 | Nest is [MIT licensed](LICENSE).
76 |
--------------------------------------------------------------------------------
/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "watch": ["src"],
3 | "ext": "ts",
4 | "ignore": ["src/**/*.spec.ts"],
5 | "exec": "ts-node -r tsconfig-paths/register src/server.ts"
6 | }
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nest-fastify",
3 | "version": "0.0.1",
4 | "license": "MIT",
5 | "scripts": {
6 | "build": "tsc -p tsconfig.build.json",
7 | "format": "prettier --write 'src/**/*.{ts,json,yml,yaml}'",
8 | "start": "ts-node -r tsconfig-paths/register src/server.ts",
9 | "start:dev": "npm run format && concurrently --handle-input \"wait-on dist/server.js && nodemon\" \"tsc -w -p tsconfig.build.json\" ",
10 | "prestart:prod": "rimraf dist && npm run build",
11 | "start:prod": "node dist/server.js",
12 | "lint": "tslint -p tsconfig.json -c tslint.json",
13 | "test": "jest",
14 | "test:watch": "jest --watch",
15 | "test:cov": "jest --coverage",
16 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
17 | "test:e2e": "jest --config ./test/jest-e2e.json"
18 | },
19 | "engines": {
20 | "node": ">=8.9.0"
21 | },
22 | "dependencies": {
23 | "@nestjs/common": "^6.0.0",
24 | "@nestjs/core": "^6.0.0",
25 | "@nestjs/platform-fastify": "^6.0.0",
26 | "@nestjs/swagger": "^3.1.0",
27 | "@nestjs/typeorm": "^6.0.0",
28 | "@types/extend": "^3.0.1",
29 | "@types/lodash": "^4.14.138",
30 | "cache-manager": "^2.10.0",
31 | "cache-manager-redis-store": "^1.5.0",
32 | "class-transformer": "^0.2.2",
33 | "class-validator": "^0.9.1",
34 | "crypto-js": "^3.1.9-1",
35 | "extend": "^3.0.2",
36 | "fastify-compress": "^0.11.0",
37 | "fastify-helmet": "^3.0.1",
38 | "fastify-rate-limit": "^2.3.0",
39 | "fastify-swagger": "^2.4.0",
40 | "lodash": "^4.17.15",
41 | "mssql": "^5.1.0",
42 | "mysql": "2.17.1",
43 | "nest-winston": "^1.1.2",
44 | "node-rsa": "^1.0.6",
45 | "reflect-metadata": "^0.1.13",
46 | "rxjs": "^6.5.2",
47 | "typeorm": "^0.2.17",
48 | "winston": "^3.2.1",
49 | "winston-daily-rotate-file": "^4.1.0"
50 | },
51 | "devDependencies": {
52 | "@nestjs/testing": "^6.7.2",
53 | "@types/faker": "latest",
54 | "@types/jest": "latest",
55 | "@types/node": "latest",
56 | "@types/pino": "latest",
57 | "@types/supertest": "latest",
58 | "concurrently": "^4.1.1",
59 | "faker": "latest",
60 | "husky": "latest",
61 | "jest": "latest",
62 | "nodemon": "latest",
63 | "pino-pretty": "latest",
64 | "prettier": "latest",
65 | "pretty-quick": "latest",
66 | "rimraf": "^2.6.3",
67 | "supertest": "latest",
68 | "ts-jest": "latest",
69 | "ts-node": "latest",
70 | "tsconfig-paths": "latest",
71 | "tslint": "latest",
72 | "tslint-config-prettier": "latest",
73 | "typescript": "^3.5.0",
74 | "wait-on": "^3.2.0"
75 | },
76 | "jest": {
77 | "moduleFileExtensions": [
78 | "js",
79 | "json",
80 | "ts"
81 | ],
82 | "rootDir": "src",
83 | "testRegex": ".spec.ts$",
84 | "transform": {
85 | "^.+\\.(t|j)s$": "ts-jest"
86 | },
87 | "coverageDirectory": "../coverage",
88 | "testEnvironment": "node"
89 | },
90 | "husky": {
91 | "hooks": {
92 | "pre-commit": "pretty-quick --staged"
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/config/base.config.ts:
--------------------------------------------------------------------------------
1 | import { IConfig } from '@interfaces/config.interface'
2 |
3 | export default (config: IConfig) => {
4 | config.crossDomain = {
5 | allowedOrigins: '*',
6 | allowedReferer: '*'
7 | }
8 |
9 | return config
10 | }
11 |
--------------------------------------------------------------------------------
/src/config/dev.config.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path'
2 | import { IConfig } from '@interfaces/config.interface'
3 |
4 | export default (config: IConfig) => {
5 | config.port = 3001
6 |
7 | config.logger = {
8 | level: 'debug',
9 | logDir: path.join(config.baseDir, 'logs')
10 | }
11 |
12 | config.redis = {
13 | host: 'localhost',
14 | port: 6379,
15 | auth_pass: '',
16 | ttl: null,
17 | db: 1,
18 | defaultCacheTTL: 60 * 60 * 24
19 | }
20 |
21 | config.typeOrm = {
22 | type: 'mssql',
23 | host: 'locahost',
24 | database: 'fuck',
25 | username: 'bitch',
26 | password: '123456',
27 | entities: [`${config.baseDir}/**/**.model.ts`],
28 | synchronize: false,
29 | migrationsRun: false,
30 | logging: ['query'],
31 | maxQueryExecutionTime: 1500 // 慢查询记录
32 | }
33 |
34 | return config
35 | }
36 |
--------------------------------------------------------------------------------
/src/config/local.config.ts:
--------------------------------------------------------------------------------
1 | import { IConfig } from '@interfaces/config.interface'
2 | import devConfig from './dev.config'
3 |
4 | export default (config: IConfig) => {
5 | return devConfig(config)
6 | }
7 |
--------------------------------------------------------------------------------
/src/config/main.config.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path'
2 | import * as extend from 'extend'
3 | import baseConfig from './base.config'
4 | import winstonConfig from './winston.config'
5 | import { IConfig } from '@interfaces/config.interface'
6 |
7 | class Config {
8 | static instance: IConfig
9 |
10 | static getInstace(): IConfig {
11 | if (!Config.instance) {
12 | Config.instance = getConfig()
13 | }
14 | return Config.instance
15 | }
16 | }
17 |
18 | function getConfig() {
19 | const env = (process.env.NODE_ENV || 'local').toLowerCase()
20 | const config: any = {
21 | env,
22 | baseDir: path.resolve(__dirname, '..', '..'),
23 | isProd: env === 'prod' || env === 'production'
24 | }
25 | const envPath = path.resolve(__dirname, `./${config.env}.config`)
26 | try {
27 | const tempConfig = extend(config, baseConfig(config))
28 | extend(config, require(envPath).default(tempConfig))
29 | winstonConfig(config)
30 | } catch (err) {
31 | throw err
32 | }
33 | return config
34 | }
35 |
36 | export default Config.getInstace()
37 |
--------------------------------------------------------------------------------
/src/config/winston.config.ts:
--------------------------------------------------------------------------------
1 | import * as winston from 'winston'
2 | import { IConfig } from '@interfaces/config.interface'
3 |
4 | require('winston-daily-rotate-file')
5 |
6 | export default (config: IConfig) => {
7 | const { level, logDir } = config.logger
8 |
9 | // @ts-ignore
10 | const transport = new winston.transports.DailyRotateFile({
11 | dirname: logDir,
12 | filename: 'app-%DATE%.log',
13 | datePattern: 'YYYY-MM-DD',
14 | zippedArchive: true,
15 | maxSize: '20m',
16 | maxFiles: '14d'
17 | })
18 |
19 | config.winston = {
20 | level,
21 | format: winston.format.combine(
22 | winston.format.colorize({
23 | colors: {
24 | info: 'blue',
25 | warn: 'yellow',
26 | debug: 'blue'
27 | }
28 | }),
29 | winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
30 | winston.format.printf(info => {
31 | if (info.stack) {
32 | info.message = info.stack
33 | }
34 | return `${info.timestamp} [${info.pid}] ${info.level}: [${info.context || 'Application'}] ${
35 | info.message
36 | }`
37 | })
38 | ),
39 | defaultMeta: { pid: process.pid },
40 | transports: [
41 | transport,
42 | new winston.transports.Console(),
43 | new winston.transports.File({ dirname: logDir, filename: 'error.log', level: 'error' })
44 | ]
45 | }
46 | return config
47 | }
48 |
--------------------------------------------------------------------------------
/src/constants/cache.constant.ts:
--------------------------------------------------------------------------------
1 | export const HOME_PAGE_CACHE = 'home_page'
2 |
--------------------------------------------------------------------------------
/src/constants/error/general.constant.ts:
--------------------------------------------------------------------------------
1 | export const DEFAULT_ERROR = {
2 | code: 100001,
3 | msg: '请求失败'
4 | }
5 |
6 | export const NOT_FOUND_ERROR = {
7 | code: 100002,
8 | msg: '找不到页面'
9 | }
10 |
11 | export const BAD_REQUEST_ERROR = {
12 | code: 100003,
13 | msg: '错误的请求'
14 | }
15 |
16 | export const FORBIDDEN_ERROR = {
17 | code: 100004,
18 | msg: '无权限访问'
19 | }
20 |
21 | export const UNAUTHORIZED_ERROR = {
22 | code: 100005,
23 | msg: '无授权访问'
24 | }
25 |
26 | export const ANONYMOUSE_ERROR = {
27 | code: 100006,
28 | msg: '非法访问'
29 | }
30 |
31 | export const VALIDATION_ERROR = {
32 | code: 100007,
33 | msg: '参数校验失败'
34 | }
35 |
--------------------------------------------------------------------------------
/src/constants/error/recharge.constant.ts:
--------------------------------------------------------------------------------
1 | export const USER_ACCOUNT_NOT_FOUND_ERROR = {
2 | code: 200001,
3 | msg: '用户账户不存在'
4 | }
5 |
--------------------------------------------------------------------------------
/src/constants/meta.constant.ts:
--------------------------------------------------------------------------------
1 | import { CACHE_KEY_METADATA } from '@nestjs/common/cache/cache.constants'
2 |
3 | export const HTTP_ERROR_RESPONSE_CODE = '__customHttpErrorResponseCode__'
4 | export const HTTP_ERROR_RESPONSE_MESSAGE = '__customHttpErrorResponseMessage__'
5 | export const HTTP_ERROR_RESPONSE_STATUS = '__customHttpErrorResponseStatus__'
6 | export const HTTP_SUCCESS_RESPONSE_CODE = '__customHttpSuccessResponseCode__'
7 | export const HTTP_SUCCESS_RESPONSE_MESSAGE = '__customHttpSuccessResponseMessage__'
8 | export const HTTP_SUCCESS_RESPONSE_STATUS = '__customHttpSuccessResponseStatus__'
9 | export const HTTP_RESPONSE_TRANSFORM_PAGINATE = '__customHttpResponseTransformPagenate__'
10 |
11 | export const HTTP_CACHE_KEY_METADATA = CACHE_KEY_METADATA
12 | export const HTTP_CACHE_TTL_METADATA = '__customHttpCacheTTL__'
13 |
--------------------------------------------------------------------------------
/src/constants/system.constant.ts:
--------------------------------------------------------------------------------
1 | export const ADMIN_NO = 'saber'
2 |
--------------------------------------------------------------------------------
/src/constants/text.constant.ts:
--------------------------------------------------------------------------------
1 | export const HTTP_ERROR_SUFFIX = '失败'
2 | export const HTTP_SUCCESS_SUFFIX = '成功'
3 | export const HTTP_DEFAULT_TEXT = '数据请求'
4 | export const HTTP_DEFAULT_ERROR_TEXT = HTTP_DEFAULT_TEXT + HTTP_ERROR_SUFFIX
5 | export const HTTP_DEFAULT_SUCCESS_TEXT = HTTP_DEFAULT_TEXT + HTTP_SUCCESS_SUFFIX
6 |
--------------------------------------------------------------------------------
/src/decorators/cache.decorator.ts:
--------------------------------------------------------------------------------
1 | import * as lodash from 'lodash'
2 | import * as META from '@constants/meta.constant'
3 | import { SetMetadata, CacheKey } from '@nestjs/common'
4 |
5 | // 缓存器配置
6 | interface ICacheOption {
7 | ttl?: number
8 | key?: string
9 | }
10 |
11 | /**
12 | * 统配构造器
13 | * @function HttpCache
14 | * @description 两种用法
15 | * @example @HttpCache(CACHE_KEY, 60 * 60)
16 | * @example @HttpCache({ key: CACHE_KEY, ttl: 60 * 60 })
17 | */
18 | export function HttpCache(option: ICacheOption): MethodDecorator
19 | export function HttpCache(key: string, ttl?: number): MethodDecorator
20 | export function HttpCache(...args: any[]) {
21 | const option = args[0]
22 | const isOption = (value): value is ICacheOption => lodash.isObject(value)
23 | const key: string = isOption(option) ? option.key : option
24 | const ttl: number = isOption(option) ? option.ttl : args[1] || null
25 | return (_, __, descriptor: PropertyDescriptor) => {
26 | if (key) {
27 | CacheKey(key)(descriptor.value)
28 | // SetMetadata(META.HTTP_CACHE_KEY_METADATA, key)(descriptor.value);
29 | }
30 | if (ttl) {
31 | SetMetadata(META.HTTP_CACHE_TTL_METADATA, ttl)(descriptor.value)
32 | }
33 | return descriptor
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/decorators/inject_service.decorator.ts:
--------------------------------------------------------------------------------
1 | export function InjectService(Repository: Function) {
2 | const metaKey = 'injected_services'
3 | return (target, name) => {
4 | const constructor = target.constructor
5 | const services = Reflect.getMetadata(metaKey, constructor) || new Map()
6 | services.set(name, Repository)
7 | Reflect.defineMetadata(metaKey, services, constructor)
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/decorators/response.decorator.ts:
--------------------------------------------------------------------------------
1 | import * as lodash from 'lodash'
2 | import * as META from '@constants/meta.constant'
3 | import * as TEXT from '@constants/text.constant'
4 | import { DEFAULT_ERROR } from '@constants/error/general.constant'
5 | import { SetMetadata, HttpStatus } from '@nestjs/common'
6 |
7 | // 构造器参数
8 | interface IBuildDecoratorOption {
9 | errorCode?: number
10 | errorStatus?: HttpStatus
11 | errorMsg?: string
12 | successCode?: number
13 | successStatus?: HttpStatus
14 | successMsg?: string
15 | usePaginate?: boolean
16 | }
17 |
18 | // handle 参数
19 | interface IHandleOption {
20 | errorCode: number
21 | errorStatus: HttpStatus
22 | message: string
23 | usePaginate?: boolean
24 | }
25 |
26 | type THandleOption = string | IHandleOption
27 |
28 | // 构造请求装饰器
29 | const buildHttpDecorator = (options: IBuildDecoratorOption): MethodDecorator => {
30 | const {
31 | errorCode,
32 | successCode,
33 | errorMsg,
34 | successMsg,
35 | errorStatus,
36 | successStatus,
37 | usePaginate
38 | } = options
39 | return (_, __, descriptor: PropertyDescriptor) => {
40 | if (errorCode) {
41 | SetMetadata(META.HTTP_ERROR_RESPONSE_CODE, errorCode)(descriptor.value)
42 | }
43 | if (successCode) {
44 | SetMetadata(META.HTTP_SUCCESS_RESPONSE_CODE, successCode)(descriptor.value)
45 | }
46 | if (errorMsg) {
47 | SetMetadata(META.HTTP_ERROR_RESPONSE_MESSAGE, errorMsg)(descriptor.value)
48 | }
49 | if (successMsg) {
50 | SetMetadata(META.HTTP_SUCCESS_RESPONSE_MESSAGE, successMsg)(descriptor.value)
51 | }
52 | if (errorStatus) {
53 | SetMetadata(META.HTTP_ERROR_RESPONSE_STATUS, errorStatus)(descriptor.value)
54 | }
55 | if (successStatus) {
56 | SetMetadata(META.HTTP_SUCCESS_RESPONSE_STATUS, successStatus)(descriptor.value)
57 | }
58 | if (usePaginate) {
59 | SetMetadata(META.HTTP_RESPONSE_TRANSFORM_PAGINATE, true)(descriptor.value)
60 | }
61 | return descriptor
62 | }
63 | }
64 |
65 | /**
66 | * 异常响应装饰器
67 | * @exports success
68 | * @example @HttpProcessor.success('error message', 100010, 200)
69 | * @example @HttpProcessor.success(DEFAULT_ERROR, 200)
70 | */
71 | export function error(
72 | message: string | { msg: string; code: number },
73 | code?: number,
74 | status?: HttpStatus
75 | ): MethodDecorator {
76 | const isString = (value): value is string => lodash.isString(value)
77 | if (isString(message)) {
78 | return buildHttpDecorator({
79 | errorMsg: message || DEFAULT_ERROR.msg,
80 | errorCode: code || DEFAULT_ERROR.code,
81 | errorStatus: status || HttpStatus.INTERNAL_SERVER_ERROR
82 | })
83 | }
84 | return buildHttpDecorator({ errorMsg: message.msg, errorCode: message.code, errorStatus: status })
85 | }
86 | /**
87 | * 成功响应装饰器
88 | * @exports success
89 | * @example @HttpProcessor.success('success message')
90 | */
91 | export const success = (message?: string, code?: number): MethodDecorator => {
92 | return buildHttpDecorator({
93 | successMsg: message || TEXT.HTTP_DEFAULT_SUCCESS_TEXT,
94 | successCode: code || HttpStatus.OK,
95 | successStatus: HttpStatus.OK
96 | })
97 | }
98 |
99 | /**
100 | * 统配构造器
101 | * @function handle
102 | * @description 两种用法
103 | * @example @HttpProcessor.handle('获取某项数据')
104 | * @example @HttpProcessor.handle({ message: '操作', errorCode: 500, errorStatus, usePaginate: true })
105 | */
106 | export function handle(args: THandleOption): MethodDecorator
107 | export function handle(...args: any[]) {
108 | const option = args[0]
109 | const isOption = (value: THandleOption): value is IHandleOption => lodash.isObject(value)
110 | const message: string = isOption(option) ? option.message : option
111 | const errorMsg: string = message + '失败'
112 | const successMsg: string = message + '成功'
113 | const errorCode: number = isOption(option) ? option.errorCode : DEFAULT_ERROR.code
114 | const errorStatus: number = isOption(option)
115 | ? option.errorStatus
116 | : HttpStatus.INTERNAL_SERVER_ERROR
117 | const successCode: number = HttpStatus.OK
118 | const usePaginate: boolean = isOption(option) ? option.usePaginate || false : false
119 | return buildHttpDecorator({
120 | errorCode,
121 | successCode,
122 | errorMsg,
123 | successMsg,
124 | errorStatus,
125 | usePaginate
126 | })
127 | }
128 |
129 | /**
130 | * 分页装饰器
131 | * @exports paginate
132 | * @example @HttpProcessor.paginate()
133 | */
134 | export const paginate = (): MethodDecorator => {
135 | return buildHttpDecorator({ usePaginate: true })
136 | }
137 |
138 | /**
139 | * 导出的不同模块
140 | * @exports HttpProcessor
141 | * @description { error, success, paginate }
142 | */
143 | export const HttpProcessor = { error, success, paginate }
144 |
--------------------------------------------------------------------------------
/src/exceptions/bad_request.error.ts:
--------------------------------------------------------------------------------
1 | import { HttpException, HttpStatus } from '@nestjs/common'
2 | import { BAD_REQUEST_ERROR } from '@constants/error/general.constant'
3 | import { TExceptionOption } from '@interfaces/response.interface'
4 |
5 | /**
6 | * @class HttpBadRequestError
7 | * @classdesc 400 -> 请求不合法
8 | */
9 | export class HttpBadRequestError extends HttpException {
10 | constructor(error?: TExceptionOption) {
11 | super(error || BAD_REQUEST_ERROR, HttpStatus.BAD_REQUEST)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/exceptions/forbidden.error.ts:
--------------------------------------------------------------------------------
1 | import { HttpException, HttpStatus } from '@nestjs/common'
2 | import { FORBIDDEN_ERROR } from '@constants/error/general.constant'
3 | import { TExceptionOption } from '@interfaces/response.interface'
4 |
5 | /**
6 | * @class HttpForbiddenError
7 | * @classdesc 403 -> 无权限/权限不足
8 | */
9 | export class HttpForbiddenError extends HttpException {
10 | constructor(error?: TExceptionOption) {
11 | super(error || FORBIDDEN_ERROR, HttpStatus.FORBIDDEN)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/exceptions/platform.error.ts:
--------------------------------------------------------------------------------
1 | import { HttpException, HttpStatus } from '@nestjs/common'
2 | import { TExceptionOption } from '@interfaces/response.interface'
3 |
4 | /**
5 | * @class PlatformError
6 | * @classdesc 默认 500 -> 服务端出错
7 | */
8 | export class PlatformError extends HttpException {
9 | constructor(options: TExceptionOption, statusCode?: HttpStatus) {
10 | super(options, statusCode || HttpStatus.INTERNAL_SERVER_ERROR)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/exceptions/unauthorized.error.ts:
--------------------------------------------------------------------------------
1 | import { UnauthorizedException } from '@nestjs/common'
2 | import { FORBIDDEN_ERROR } from '@constants/error/general.constant'
3 | import { TExceptionOption } from '@interfaces/response.interface'
4 |
5 | /**
6 | * @class HttpUnauthorizedError
7 | * @classdesc 401 -> 未授权/权限验证失败
8 | */
9 | export class HttpUnauthorizedError extends UnauthorizedException {
10 | constructor(message?: TExceptionOption, error?: string) {
11 | super(message || FORBIDDEN_ERROR, error)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/exceptions/validation.error.ts:
--------------------------------------------------------------------------------
1 | import { HttpException, HttpStatus } from '@nestjs/common'
2 | import { VALIDATION_ERROR } from '@constants/error/general.constant'
3 | import { TExceptionOption } from '@interfaces/response.interface'
4 |
5 | /**
6 | * @class ValidationError
7 | * @classdesc 400 -> 请求有问题,这个错误经常发生在错误内层,所以 code 没有意义
8 | */
9 | export class ValidationError extends HttpException {
10 | constructor(error?: TExceptionOption) {
11 | super(error || VALIDATION_ERROR, HttpStatus.BAD_REQUEST)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/filters/exception.filter.ts:
--------------------------------------------------------------------------------
1 | import * as lodash from 'lodash'
2 | import { Logger } from 'winston'
3 | import { ExceptionFilter, Catch, HttpException, ArgumentsHost, HttpStatus } from '@nestjs/common'
4 | import { THttpErrorResponse, TExceptionOption } from '@interfaces/response.interface'
5 | import { NOT_FOUND_ERROR, DEFAULT_ERROR } from '@constants/error/general.constant'
6 |
7 | /**
8 | * @class HttpExceptionFilter
9 | * @classdesc 拦截全局抛出的所有异常,同时任何错误将在这里被规范化输出 THttpErrorResponse
10 | */
11 | @Catch()
12 | export class HttpExceptionFilter implements ExceptionFilter {
13 | constructor(private readonly logger: Logger) {}
14 |
15 | catch(exception: HttpException, host: ArgumentsHost) {
16 | this.logger.error(exception.toString())
17 | const request = host.switchToHttp().getRequest().req
18 | const response = host.switchToHttp().getResponse()
19 | try {
20 | const status = exception.getStatus() || HttpStatus.INTERNAL_SERVER_ERROR
21 | const errorOption: TExceptionOption = exception.getResponse() as TExceptionOption
22 | const isString = (value): value is string => lodash.isString(value)
23 | const errCode = isString(errorOption)
24 | ? NOT_FOUND_ERROR.code
25 | : errorOption.code || NOT_FOUND_ERROR.code
26 | const errMessage = isString(errorOption) ? errorOption : errorOption.msg
27 | const errorInfo = isString(errorOption) ? null : errorOption.error
28 | const parentErrorInfo = errorInfo ? String(errorInfo) : null
29 | const isChildrenError = errorInfo && errorInfo.code && errorInfo.msg
30 | const resultError = (isChildrenError && errorInfo.msg) || parentErrorInfo
31 | const data: THttpErrorResponse = {
32 | code: errCode,
33 | msg: errMessage,
34 | error: resultError
35 | }
36 | // 对默认的 404 进行特殊处理
37 | if (status === HttpStatus.NOT_FOUND) {
38 | data.error = `资源不存在`
39 | data.msg = `接口 ${request.method} -> ${request.url} 无效`
40 | }
41 | return response.code(status).send(data)
42 | } catch (err) {
43 | return response.code(status).send(DEFAULT_ERROR)
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/interceptors/cache.interceptor.ts:
--------------------------------------------------------------------------------
1 | import { tap } from 'rxjs/operators'
2 | import { Observable, of } from 'rxjs'
3 | import { Logger } from 'winston'
4 | import {
5 | CacheInterceptor,
6 | ExecutionContext,
7 | CallHandler,
8 | Injectable,
9 | Inject,
10 | RequestMethod
11 | } from '@nestjs/common'
12 | import { FastifyAdapter } from '@nestjs/platform-fastify'
13 | import * as META from '@constants/meta.constant'
14 | import config from '@config'
15 |
16 | /**
17 | * @class HttpCacheInterceptor
18 | * @classdesc 自定义这个拦截器是是要弥补框架不支持 ttl 参数的缺陷
19 | */
20 | @Injectable()
21 | export class HttpCacheInterceptor extends CacheInterceptor {
22 | constructor(cacheManager: any, reflector: any, @Inject('winston') private logger: Logger) {
23 | super(cacheManager, reflector)
24 | }
25 | // 自定义装饰器,修饰 ttl 参数
26 | async intercept(context: ExecutionContext, next: CallHandler): Promise> {
27 | const call$ = next.handle()
28 | // 如果想彻底禁用缓存服务,还是直接返回数据吧
29 | // return call$;
30 | const key = this.trackBy(context)
31 | const target = context.getHandler()
32 | const request = context.switchToHttp().getRequest().req
33 | const metaTTL = this.reflector.get(META.HTTP_CACHE_TTL_METADATA, target)
34 | const ttl = metaTTL || config.redis.ttl
35 | if (!key) {
36 | return call$
37 | }
38 | try {
39 | const value = await this.cacheManager.get(key)
40 | if (value) {
41 | this.logger.info(
42 | `返回缓存数据:${request.method} -> ${request.url} ${JSON.stringify(value)}`
43 | )
44 | return of(value)
45 | }
46 | return call$.pipe(tap(response => this.cacheManager.set(key, response, { ttl })))
47 | } catch (error) {
48 | return call$
49 | }
50 | }
51 |
52 | /**
53 | * @function trackBy
54 | * @description 目前的命中规则是:必须手动设置了 CacheKey 才会启用缓存机制,默认 ttl 为 APP_CONFIG.REDIS.defaultCacheTTL
55 | */
56 | trackBy(context: ExecutionContext): string | undefined {
57 | const request = context.switchToHttp().getRequest()
58 | const httpServer: FastifyAdapter = this.httpAdapterHost.httpAdapter
59 | const isHttpApp = httpServer && !!httpServer.getRequestMethod
60 | const isGetRequest =
61 | isHttpApp && httpServer.getRequestMethod(request) === RequestMethod[RequestMethod.GET]
62 | const requestUrl = httpServer.getRequestUrl(request)
63 | const cacheKey = this.reflector.get(META.HTTP_CACHE_KEY_METADATA, context.getHandler())
64 | const isMatchedCache = isHttpApp && isGetRequest && cacheKey
65 | // 缓存命中策略 -> http -> GET -> cachekey -> url -> undefined;
66 | return isMatchedCache ? cacheKey || requestUrl : undefined
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/interceptors/error.interceptor.ts:
--------------------------------------------------------------------------------
1 | import { Observable, throwError } from 'rxjs'
2 | import { catchError } from 'rxjs/operators'
3 | import { Logger } from 'winston'
4 | import { Reflector } from '@nestjs/core'
5 | import {
6 | Injectable,
7 | NestInterceptor,
8 | CallHandler,
9 | ExecutionContext,
10 | HttpStatus
11 | } from '@nestjs/common'
12 | import { PlatformError } from '@exceptions/platform.error'
13 | import { DEFAULT_ERROR } from '@constants/error/general.constant'
14 | import * as META from '@constants/meta.constant'
15 | import * as TEXT from '@constants/text.constant'
16 |
17 | /**
18 | * @class ErrorInterceptor
19 | * @classdesc 当控制器所需的 Promise service 发生错误时,错误将在此被捕获
20 | */
21 | @Injectable()
22 | export class ErrorInterceptor implements NestInterceptor {
23 | constructor(private readonly reflector: Reflector, readonly logger: Logger) { }
24 | intercept(context: ExecutionContext, next: CallHandler): Observable {
25 | const call$ = next.handle()
26 | const target = context.getHandler()
27 | const code =
28 | this.reflector.get(META.HTTP_SUCCESS_RESPONSE_CODE, target) || DEFAULT_ERROR.code
29 | const status =
30 | this.reflector.get(META.HTTP_ERROR_RESPONSE_STATUS, target) ||
31 | HttpStatus.INTERNAL_SERVER_ERROR
32 | const msg =
33 | this.reflector.get(META.HTTP_ERROR_RESPONSE_MESSAGE, target) ||
34 | TEXT.HTTP_DEFAULT_ERROR_TEXT
35 | return call$.pipe(
36 | catchError(error => {
37 | if (error.response && error.status) {
38 | return throwError(error)
39 | }
40 | return throwError(
41 | new PlatformError(
42 | {
43 | code: Number(code),
44 | msg,
45 | error: error.message
46 | },
47 | status
48 | )
49 | )
50 | })
51 | )
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/interceptors/logging.interceptor.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from 'rxjs'
2 | import { tap } from 'rxjs/operators'
3 | import { Logger } from 'winston'
4 | import { Injectable, NestInterceptor, CallHandler, ExecutionContext } from '@nestjs/common'
5 |
6 | @Injectable()
7 | export class LoggingInterceptor implements NestInterceptor {
8 | constructor(private readonly logger: Logger) {}
9 |
10 | intercept(context: ExecutionContext, next: CallHandler): Observable {
11 | const call$ = next.handle()
12 | const request = context.switchToHttp().getRequest().req
13 | const content = request.method + ' -> ' + request.url
14 | const body = request.body ? JSON.stringify(request.body) : '{}'
15 | this.logger.info(`收到请求:${content} body: ${body}`)
16 | const now = Date.now()
17 | return call$.pipe(tap(() => this.logger.info(`响应请求:${content} ${Date.now() - now}ms`)))
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/interceptors/transform.interceptor.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from 'rxjs'
2 | import { map } from 'rxjs/operators'
3 | import { Reflector } from '@nestjs/core'
4 | import {
5 | Injectable,
6 | NestInterceptor,
7 | CallHandler,
8 | ExecutionContext,
9 | HttpStatus
10 | } from '@nestjs/common'
11 | import { THttpSuccessResponse, IHttpResultPaginate } from '@interfaces/response.interface'
12 | import * as META from '@constants/meta.constant'
13 | import * as TEXT from '@constants/text.constant'
14 |
15 | // 转换为标准的数据结构
16 | export function transformDataToPaginate(data: {
17 | rows: T[]
18 | count: number
19 | }): IHttpResultPaginate {
20 | return {
21 | list: data.rows,
22 | total: data.count
23 | }
24 | }
25 |
26 | /**
27 | * @class TransformInterceptor
28 | * @classdesc 当控制器所需的 Promise service 成功响应时,将在此被转换为标准的数据结构 IHttpResultPaginate
29 | */
30 | @Injectable()
31 | export class TransformInterceptor implements NestInterceptor> {
32 | constructor(private readonly reflector: Reflector) {}
33 | intercept(context: ExecutionContext, next: CallHandler): Observable> {
34 | const call$ = next.handle()
35 | const target = context.getHandler()
36 | const code =
37 | this.reflector.get(META.HTTP_SUCCESS_RESPONSE_CODE, target) || HttpStatus.OK
38 | const msg =
39 | this.reflector.get(META.HTTP_SUCCESS_RESPONSE_MESSAGE, target) ||
40 | TEXT.HTTP_DEFAULT_SUCCESS_TEXT
41 | const usePaginate = this.reflector.get(META.HTTP_RESPONSE_TRANSFORM_PAGINATE, target)
42 | return call$.pipe(
43 | map((item: any) => {
44 | const data = !usePaginate ? item : transformDataToPaginate(item)
45 | return { code: Number(code), msg, data }
46 | })
47 | )
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/interfaces/config.interface.ts:
--------------------------------------------------------------------------------
1 | import { LoggerOptions } from 'winston'
2 | import { ConnectionOptions } from 'typeorm'
3 |
4 | export interface IConfig {
5 | readonly env: string
6 | readonly isProd: boolean
7 | readonly baseDir: string
8 | port: number
9 | crossDomain: {
10 | allowedOrigins: string | string[]
11 | allowedReferer: string | string[]
12 | }
13 | typeOrm: ConnectionOptions
14 | winston: LoggerOptions
15 | logger: {
16 | level?: string
17 | logDir?: string
18 | }
19 | [propName: string]: any
20 | }
21 |
--------------------------------------------------------------------------------
/src/interfaces/response.interface.ts:
--------------------------------------------------------------------------------
1 | export type TExceptionOption =
2 | | string
3 | | {
4 | code: number
5 | msg: string
6 | error?: any
7 | }
8 |
9 | // 分页数据
10 | export interface IHttpResultPaginate {
11 | list: T
12 | total: number
13 | }
14 |
15 | // HTTP 状态返回
16 | export interface IHttpResponseBase {
17 | code: number
18 | msg: string
19 | }
20 |
21 | // HTTP error
22 | export type THttpErrorResponse = IHttpResponseBase & {
23 | error: any
24 | }
25 |
26 | // HTTP success 返回
27 | export type THttpSuccessResponse = IHttpResponseBase & {
28 | data: T | IHttpResultPaginate
29 | }
30 |
31 | // HTTP Response
32 | export type THttpResponse = THttpErrorResponse | THttpSuccessResponse
33 |
--------------------------------------------------------------------------------
/src/middlewares/cors.middleware.ts:
--------------------------------------------------------------------------------
1 | import { ServerResponse } from 'http'
2 | import { Injectable, NestMiddleware, HttpStatus, RequestMethod } from '@nestjs/common'
3 | import config from '@config'
4 |
5 | /**
6 | * @class CorsMiddleware
7 | * @classdesc 用于处理 CORS 跨域
8 | */
9 | @Injectable()
10 | export class CorsMiddleware implements NestMiddleware {
11 | use(request, response: ServerResponse, next) {
12 | const { crossDomain } = config
13 | const getMethod = method => RequestMethod[method]
14 | const origins = request.headers.origin
15 | const origin = (Array.isArray(origins) ? origins[0] : origins) || ''
16 | const allowedOrigins = [...crossDomain.allowedOrigins]
17 | const allowedMethods = [
18 | RequestMethod.GET,
19 | RequestMethod.HEAD,
20 | RequestMethod.PUT,
21 | RequestMethod.PATCH,
22 | RequestMethod.POST,
23 | RequestMethod.DELETE
24 | ]
25 | const allowedHeaders = [
26 | 'Authorization',
27 | 'Origin',
28 | 'No-Cache',
29 | 'X-Requested-With',
30 | 'If-Modified-Since',
31 | 'Pragma',
32 | 'Last-Modified',
33 | 'Cache-Control',
34 | 'Expires',
35 | 'Content-Type',
36 | 'X-E4M-With'
37 | ]
38 |
39 | // Allow Origin
40 | if (!origin || allowedOrigins.includes(origin)) {
41 | response.setHeader('Access-Control-Allow-Origin', origin || '*')
42 | }
43 | // Headers
44 | response.setHeader('Access-Control-Allow-Headers', allowedHeaders.join(','))
45 | response.setHeader('Access-Control-Allow-Methods', allowedMethods.map(getMethod).join(','))
46 | response.setHeader('Access-Control-Max-Age', '1728000')
47 | response.setHeader('Content-Type', 'application/json; charset=utf-8')
48 |
49 | // OPTIONS Request
50 | if (request.method === getMethod(RequestMethod.OPTIONS)) {
51 | response.statusCode = HttpStatus.NO_CONTENT
52 | response.end()
53 | } else {
54 | return next()
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/middlewares/origin.middleware.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, NestMiddleware, HttpStatus } from '@nestjs/common'
2 | import { ANONYMOUSE_ERROR } from '@constants/error/general.constant'
3 | import config from '@config'
4 |
5 | /**
6 | * @class OriginMiddleware
7 | * @classdesc 用于验证是否为非法来源请求
8 | */
9 | @Injectable()
10 | export class OriginMiddleware implements NestMiddleware {
11 | use(request, response, next) {
12 | const { crossDomain } = config
13 | const { origin, referer } = request.headers
14 | const checkHeader = field =>
15 | !field || crossDomain.allowedReferer === '*' || crossDomain.allowedReferer.includes(field)
16 | const isVerifiedOrigin = checkHeader(origin)
17 | const isVerifiedReferer = checkHeader(referer)
18 | if (!isVerifiedOrigin && !isVerifiedReferer) {
19 | response.statusCode = HttpStatus.UNAUTHORIZED
20 | return response.end(
21 | JSON.stringify({
22 | ...ANONYMOUSE_ERROR
23 | })
24 | )
25 | }
26 | // 其他通行
27 | return next()
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/models/anothor_lifeblood.model.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 联机账户表
3 | * 想疯狂打人!!!!!!1
4 | */
5 | import { Entity, Column, UpdateDateColumn } from 'typeorm'
6 |
7 | @Entity({
8 | name: 'dt_ac_user'
9 | })
10 | export class AnotherLifeblood {
11 | @Column({
12 | primary: true,
13 | name: 'user_serial',
14 | type: 'bigint',
15 | comment: '用户id;这是主键。。。。'
16 | })
17 | userSerial?: number
18 |
19 | @Column({
20 | name: 'ac_money',
21 | type: 'money',
22 | comment: '账户总余额(分),现金+补贴'
23 | })
24 | totalBalance?: number
25 |
26 | @Column({
27 | name: 'ac_addo',
28 | type: 'money',
29 | comment: '现金总余额(分)'
30 | })
31 | moneyBalance?: number
32 |
33 | @Column({
34 | name: 'ac_subo',
35 | type: 'money',
36 | comment: '补贴总余额(分)'
37 | })
38 | subsidyBalance?: number
39 |
40 | @Column({
41 | name: 'ac_addm',
42 | type: 'money',
43 | comment: '累计现金充值余额(分)'
44 | })
45 | accAmont?: number
46 |
47 | @Column({
48 | name: 'ac_subm',
49 | type: 'money',
50 | comment: '累计补贴充值余额(分)'
51 | })
52 | accSubsidy?: number
53 |
54 | @UpdateDateColumn({
55 | name: 'sj',
56 | type: 'datetime',
57 | comment: '修改时间'
58 | })
59 | updatedDate?: Date
60 | }
61 |
--------------------------------------------------------------------------------
/src/models/assist.model.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 补贴充值记录表
3 | * 想疯狂打人!!!!!!1
4 | */
5 | import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'
6 |
7 | import { ADMIN_NO } from '@constants/system.constant'
8 |
9 | export enum SubsidyMode {
10 | MISAKA_NO_0 = 0, // 手动金额补贴
11 | MISAKA_NO_1 = 1, // 自动金额补贴
12 | MISAKA_NO_2 = 2, // 提现转入已发放
13 | MISAKA_NO_3 = 3, // 提现转入未发放
14 | MISAKA_NO_4 = 4, // 旧卡金额录入
15 | MISAKA_NO_5 = 5, // 纠错录入
16 | MISAKA_NO_6 = 6, // 补卡转入已发补贴
17 | MISAKA_NO_7 = 7, // 补卡转入未发补贴
18 | MISAKA_NO_8 = 8 // 银行app充值
19 | }
20 |
21 | export enum RechargeType {
22 | COWARD = 0, // 脱机
23 | HERO = 1 // 联机
24 | }
25 |
26 | export const enum IsCleanUp {
27 | FUCK_NOW = 1, // 清除
28 | NO_FUCK = 0 // 不清除
29 | }
30 |
31 | export const enum SubsidyStatus {
32 | WAIT = 0, // 未发送充值
33 | SENDED = 1, // 已发送充值
34 | SUCCESS = 2, // 充值成功
35 | FAIL = 3 // 充值失败
36 | }
37 |
38 | @Entity({
39 | name: 'xf_subsidy'
40 | })
41 | export class Assist {
42 | @PrimaryGeneratedColumn('increment', {
43 | name: 'xh'
44 | })
45 | id?: number
46 |
47 | @Column({
48 | name: 'sub_money',
49 | type: 'money',
50 | comment: '补贴金额(分)'
51 | })
52 | subAmount?: number
53 |
54 | @Column({
55 | name: 'sj',
56 | type: 'datetime',
57 | comment: '创建时间'
58 | })
59 | createdDate?: Date
60 |
61 | @Column({
62 | name: 'update_sj',
63 | type: 'datetime',
64 | comment: '修改时间'
65 | })
66 | updatedDate?: Date
67 |
68 | @Column({
69 | name: 'lx',
70 | type: 'int',
71 | default: SubsidyMode.MISAKA_NO_8,
72 | comment: '补贴方式'
73 | })
74 | subsidyMode?: SubsidyMode
75 |
76 | @Column({
77 | name: 'sub_state',
78 | type: 'int',
79 | default: SubsidyStatus.WAIT,
80 | comment: '补贴状态'
81 | })
82 | status?: SubsidyStatus
83 |
84 | @Column({
85 | name: 'sub_new',
86 | type: 'money',
87 | comment: '补贴余额(分),不知道啥意思,咱也不敢问,再也不敢说,反正为0就行了',
88 | default: 0
89 | })
90 | subBalance?: number
91 |
92 | @Column({
93 | name: 'sub_del',
94 | type: 'int',
95 | comment: '是否清除之前的补贴;0不清,1清',
96 | default: IsCleanUp.NO_FUCK
97 | })
98 | isCleanUp?: IsCleanUp
99 |
100 | @Column({
101 | name: 'gly_no',
102 | type: 'nvarchar',
103 | length: 50,
104 | nullable: true,
105 | default: ADMIN_NO,
106 | comment: '操作人员编号'
107 | })
108 | operatorCode?: string
109 |
110 | @Column({
111 | type: 'varchar',
112 | length: 50,
113 | comment: '交易流水号'
114 | })
115 | tranno?: string
116 |
117 | @Column({
118 | name: 'type',
119 | type: 'int',
120 | default: RechargeType.HERO,
121 | comment: '充值类型,0脱机,1联机'
122 | })
123 | rechargeType?: RechargeType
124 |
125 | @Column({
126 | name: 'sub_order',
127 | type: 'int',
128 | comment: '序号,当前人最大序号+1。。。'
129 | })
130 | order?: number
131 |
132 | @Column({
133 | name: 'user_serial',
134 | type: 'bigint',
135 | comment: '用户id'
136 | })
137 | userSerial?: number
138 | }
139 |
--------------------------------------------------------------------------------
/src/models/lifeblood.model.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 联机账户表
3 | * 想疯狂打人!!!!!!1
4 | */
5 | import { Entity, Column, PrimaryGeneratedColumn, UpdateDateColumn, VersionColumn } from 'typeorm'
6 |
7 | @Entity({
8 | name: 'dt_ac_link'
9 | })
10 | export class Lifeblood {
11 | @PrimaryGeneratedColumn('increment', {
12 | name: 'xh'
13 | })
14 | id?: number
15 |
16 | @Column({
17 | name: 'ac_money',
18 | type: 'money',
19 | comment: '账户总余额(分),现金+补贴'
20 | })
21 | totalBalance?: number
22 |
23 | @Column({
24 | name: 'ac_addo',
25 | type: 'money',
26 | comment: '现金总余额(分)'
27 | })
28 | moneyBalance?: number
29 |
30 | @Column({
31 | name: 'ac_subo',
32 | type: 'money',
33 | comment: '补贴总余额(分)'
34 | })
35 | subsidyBalance?: number
36 |
37 | @Column({
38 | name: 'ac_eacho',
39 | type: 'int',
40 | comment: '账户份数余额'
41 | })
42 | totalCopies?: number
43 |
44 | @VersionColumn({
45 | name: 'jl_count',
46 | type: 'int',
47 | comment: '修改次数'
48 | })
49 | modifyCount?: number
50 |
51 | @UpdateDateColumn({
52 | name: 'sj',
53 | type: 'datetime',
54 | comment: '修改时间'
55 | })
56 | updatedDate?: Date
57 |
58 | @Column({
59 | name: 'user_serial',
60 | type: 'bigint',
61 | comment: '用户id'
62 | })
63 | userSerial?: number
64 | }
65 |
--------------------------------------------------------------------------------
/src/models/monster.model.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 现金充值记录表
3 | * 想疯狂打人!!!!!!1
4 | */
5 | import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'
6 |
7 | export const enum StrengthenStatus {
8 | WAIT = 0, // 未发送充值
9 | SENDED = 1, // 已发送充值
10 | SUCCESS = 2, // 充值成功
11 | FAIL = 3 // 充值失败
12 | }
13 |
14 | export enum RechargeMode {
15 | ONE_BOOLD = 0, // 软件读卡充值
16 | DOUBLE_KILL = 1, // 充值名单充值
17 | TRIPLE_KILL = 2, // 提现现金充值
18 | QUADRA_KILL = 3, // 补卡充值
19 | PENTA_KILL = 4 // 银行充值
20 | }
21 |
22 | export enum RechargeType {
23 | COWARD = 0, // 脱机
24 | HERO = 1 // 联机
25 | }
26 |
27 | @Entity({
28 | name: 'dt_user'
29 | })
30 | export class Monster {
31 | @PrimaryGeneratedColumn('increment', {
32 | name: 'user_serial',
33 | type: 'bigint',
34 | })
35 | id?: number
36 |
37 | @Column({
38 | name: 'user_no',
39 | type: 'varchar',
40 | comment: '用户编号'
41 | })
42 | userNo?: number
43 |
44 | @Column({
45 | name: 'dep_no',
46 | type: 'varchar',
47 | comment: 'string'
48 | })
49 | depNo?: string
50 |
51 | @Column({
52 | name: 'user_lname',
53 | type: 'varchar',
54 | comment: '用户名'
55 | })
56 | username?: string
57 | }
58 |
--------------------------------------------------------------------------------
/src/models/naked.model.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 原始消费记录表
3 | * 想疯狂打人!!!!!!1
4 | */
5 | import { Entity, Column, CreateDateColumn, PrimaryGeneratedColumn } from 'typeorm'
6 |
7 | import { ADMIN_NO } from '@constants/system.constant'
8 |
9 | export const enum Mould {
10 | SWIM = 0, // 现金模式
11 | KEEP = 1 // 计份模式
12 | }
13 |
14 | export const enum Status {
15 | GENUINE = 0, // 有效记录
16 | FORGERY = 1 // 无效记录
17 | }
18 |
19 | export const enum RecordType {
20 | SABER_NO_1 = 1, // 消费
21 | SABER_NO_2 = 2, // 正常充值
22 | SABER_NO_3 = 3, // 提现金额充值
23 | SABER_NO_4 = 4, // 补贴
24 | SABER_NO_5 = 5, // 纠错补贴
25 | SABER_NO_8 = 8, // 提现
26 | SABER_NO_12 = 12, // 无卡充值
27 | SABER_NO_13 = 13, // 补卡充值
28 | SABER_NO_16 = 16, // 补贴清零
29 | SABER_NO_31 = 31, // 金额消费纠错
30 | SABER_NO_32 = 32, // 冲正
31 | SABER_NO_34 = 34, // 日补贴
32 | SABER_NO_35 = 35, // 时段补贴
33 | SABER_NO_36 = 36, // 冻结
34 | SABER_NO_37 = 37, // 解冻
35 | SABER_NO_51 = 51, // 联机记账
36 | SABER_NO_52 = 52, // 联机份记账
37 | SABER_NO_61 = 61 // 银行app充值
38 | }
39 |
40 | export const enum RecordSource {
41 | LUCKY = 0, // 设备读取的记录
42 | LARRY = 1, // 系统生成的记录
43 | ANDY = 2, // 系统补录的记录
44 | CHERRY = 3, // 手工补录的记录
45 | HONNY = 4, // 联机收到的记录
46 | TONY = 5 // 银行app充值
47 | }
48 |
49 | @Entity({
50 | name: 'xf_jl'
51 | })
52 | export class Naked {
53 | @PrimaryGeneratedColumn('increment', {
54 | name: 'xh'
55 | })
56 | id?: number
57 |
58 | @Column({
59 | name: 'mould',
60 | type: 'int',
61 | default: Mould.SWIM,
62 | comment: '消费模式,0:现金模式 1:计份模式'
63 | })
64 | mould?: Mould
65 |
66 | @Column({
67 | name: 'lx',
68 | type: 'int',
69 | default: RecordType.SABER_NO_61,
70 | comment: '记录类型'
71 | })
72 | recordType?: RecordType
73 |
74 | @Column({
75 | name: 'jl_bh',
76 | type: 'varchar',
77 | length: 50,
78 | default: '0',
79 | comment: '记录编号'
80 | })
81 | recordNo?: string
82 |
83 | @Column({
84 | type: 'varchar',
85 | length: 50,
86 | comment: '交易流水号'
87 | })
88 | tranno?: string
89 |
90 | @Column({
91 | name: 'dev_serial',
92 | type: 'char',
93 | default: '0000000',
94 | comment: '设备id'
95 | })
96 | devSerial?: string
97 |
98 | @Column({
99 | name: 'sam_serial',
100 | type: 'varchar',
101 | default: '0000000',
102 | comment: 'SAM卡应用序列号:00+SAM卡前5个字节。12位十进制'
103 | })
104 | samSerial?: string
105 |
106 | @Column({
107 | name: 'dep_serial',
108 | type: 'bigint',
109 | comment: '部门id'
110 | })
111 | depSerial?: number
112 |
113 | @Column({
114 | name: 'old_money',
115 | type: 'money',
116 | comment: '交易前总余额(分)'
117 | })
118 | preTotalBalance?: number
119 |
120 | @Column({
121 | name: 'new_money',
122 | type: 'money',
123 | default: 0,
124 | comment: '消费/充值金额(分)'
125 | })
126 | amount?: number
127 |
128 | @Column({
129 | name: 'user_serial',
130 | type: 'bigint',
131 | comment: '用户id'
132 | })
133 | userSerial?: number
134 |
135 | @Column({
136 | name: 'sub_xh',
137 | type: 'int',
138 | nullable: true,
139 | comment: '补贴id'
140 | })
141 | assistId?: number
142 |
143 | @Column({
144 | name: 'state',
145 | type: 'int',
146 | comment: '记录状态,0有效,1无效',
147 | default: Status.GENUINE
148 | })
149 | status?: Status
150 |
151 | @Column({
152 | name: 'type',
153 | type: 'int',
154 | comment: '记录来源',
155 | default: RecordSource.TONY
156 | })
157 | recordSource?: RecordSource
158 |
159 | @Column({
160 | name: 'time_order',
161 | type: 'char',
162 | comment: '设备时段编号',
163 | default: '0000000000000000'
164 | })
165 | devTimeNo?: string
166 |
167 | @Column({
168 | name: 'time_no',
169 | type: 'char',
170 | comment: '个人时段编号',
171 | default: '0000000000000000'
172 | })
173 | userTimeNo?: string
174 |
175 | @Column({
176 | name: 'del_type',
177 | type: 'int',
178 | comment: '是否删除',
179 | default: 0
180 | })
181 | isDelete?: number
182 |
183 | @Column({
184 | name: 'sub_old',
185 | type: 'money',
186 | comment: '补贴交易前余额(分)'
187 | })
188 | preSubsidyBalance?: number
189 |
190 | @Column({
191 | name: 'sub_new',
192 | type: 'money',
193 | default: 0,
194 | comment: '消费/充值补贴(分)'
195 | })
196 | subsidy?: number
197 |
198 | @Column({
199 | name: 'each_old',
200 | type: 'int',
201 | comment: '交易前份数',
202 | default: 0
203 | })
204 | preCopiesNum?: number
205 |
206 | @Column({
207 | name: 'each_new',
208 | type: 'int',
209 | comment: '操作份数',
210 | default: 0
211 | })
212 | copiesNum?: number
213 |
214 | @Column({
215 | name: 'each_unit',
216 | type: 'int',
217 | default: 0,
218 | comment: '交易单价(分)'
219 | })
220 | unitPrice?: number
221 |
222 | @Column({
223 | name: 'more_money',
224 | type: 'money',
225 | default: 0,
226 | comment: '应收金额(分)'
227 | })
228 | amountReceivable?: number
229 |
230 | @Column({
231 | name: 'discount_rate',
232 | type: 'int',
233 | default: 100,
234 | comment: '消费折扣比例,百分比数字'
235 | })
236 | discountRate?: number
237 |
238 | @Column({
239 | name: 'jl_count',
240 | type: 'int',
241 | comment: '修改次数'
242 | })
243 | modifyCount?: number
244 |
245 | @CreateDateColumn({
246 | name: 'jl_sj',
247 | type: 'int',
248 | comment: '记录时间'
249 | })
250 | createdDate?: Date
251 |
252 | @Column({
253 | name: 'sj',
254 | type: 'datetime',
255 | comment: '修改时间'
256 | })
257 | updatedDate?: Date
258 |
259 | @Column({
260 | name: 'gly_no',
261 | type: 'nvarchar',
262 | length: 50,
263 | nullable: true,
264 | default: ADMIN_NO,
265 | comment: '操作人员编号'
266 | })
267 | operatorCode?: string
268 | }
269 |
--------------------------------------------------------------------------------
/src/models/strengthen.model.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 现金充值记录表
3 | * 想疯狂打人!!!!!!1
4 | */
5 | import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'
6 | import { ADMIN_NO } from '@constants/system.constant'
7 |
8 | export const enum StrengthenStatus {
9 | WAIT = 0, // 未发送充值
10 | SENDED = 1, // 已发送充值
11 | SUCCESS = 2, // 充值成功
12 | FAIL = 3 // 充值失败
13 | }
14 |
15 | export enum RechargeMode {
16 | ONE_BOOLD = 0, // 软件读卡充值
17 | DOUBLE_KILL = 1, // 充值名单充值
18 | TRIPLE_KILL = 2, // 提现现金充值
19 | QUADRA_KILL = 3, // 补卡充值
20 | PENTA_KILL = 4 // 银行充值
21 | }
22 |
23 | export enum RechargeType {
24 | COWARD = 0, // 脱机
25 | HERO = 1 // 联机
26 | }
27 |
28 | @Entity({
29 | name: 'xf_addmoney'
30 | })
31 | export class Strengthen {
32 | @PrimaryGeneratedColumn('increment', {
33 | name: 'xh'
34 | })
35 | id?: number
36 |
37 | @Column({
38 | name: 'input_money',
39 | type: 'money',
40 | comment: '输入金额(分)'
41 | })
42 | inputMoney?: number
43 |
44 | @Column({
45 | name: 'add_money',
46 | type: 'money',
47 | comment: '实际金额(分)'
48 | })
49 | realMoney?: number
50 |
51 | @Column({
52 | name: 'gly_money',
53 | type: 'money',
54 | default: 0,
55 | nullable: true,
56 | comment: '管理费?手续费?(分)'
57 | })
58 | commission?: number
59 |
60 | @Column({
61 | name: 'sj',
62 | type: 'datetime',
63 | comment: '创建时间'
64 | })
65 | createdDate?: Date
66 |
67 | @Column({
68 | name: 'update_sj',
69 | type: 'datetime',
70 | comment: '修改时间'
71 | })
72 | updatedDate?: Date
73 |
74 | @Column({
75 | name: 'add_state',
76 | type: 'int',
77 | default: StrengthenStatus.SUCCESS,
78 | comment: '充值状态'
79 | })
80 | status?: StrengthenStatus
81 |
82 | @Column({
83 | name: 'gly_no',
84 | type: 'nvarchar',
85 | length: 50,
86 | nullable: true,
87 | default: ADMIN_NO,
88 | comment: '操作人员编号'
89 | })
90 | operatorCode?: string
91 |
92 | @Column({
93 | name: 'lx',
94 | type: 'int',
95 | default: RechargeMode.PENTA_KILL,
96 | comment: '充值方式'
97 | })
98 | rechargeMode?: RechargeMode
99 |
100 | @Column({
101 | name: 'type',
102 | type: 'int',
103 | default: RechargeType.HERO,
104 | comment: '充值类型,0脱机,1联机'
105 | })
106 | rechargeType?: RechargeType
107 |
108 | @Column({
109 | type: 'varchar',
110 | length: 50,
111 | comment: '交易流水号'
112 | })
113 | tranno?: string
114 |
115 | @Column({
116 | name: 'user_serial',
117 | type: 'bigint',
118 | comment: '用户id'
119 | })
120 | userSerial?: number
121 |
122 | // @Column({
123 | // name: 'bank_id',
124 | // type: 'int',
125 | // comment: '银行id'
126 | // })
127 | // bankId?: number;
128 |
129 | // @Column({
130 | // name: 'recharge_user',
131 | // type: 'int',
132 | // comment: '充值人id'
133 | // })
134 | // recharge_user?: number;
135 | }
136 |
--------------------------------------------------------------------------------
/src/models/undisguised.model.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 消费明细记录表
3 | * 想疯狂打人!!!!!!1
4 | */
5 | import {
6 | Entity,
7 | Column,
8 | PrimaryGeneratedColumn,
9 | BeforeInsert
10 | } from 'typeorm'
11 | import { ADMIN_NO } from '@constants/system.constant'
12 |
13 | export const enum RecordSource {
14 | BITCH = 0, // 脱机
15 | DOG = 1 // 联机
16 | }
17 |
18 | export const enum Mould {
19 | SWIM = 0, // 现金模式
20 | KEEP = 1 // 计份模式
21 | }
22 |
23 | export const enum RecordType {
24 | SABER_NO_1 = 1, // 消费
25 | SABER_NO_2 = 2, // 正常充值
26 | SABER_NO_3 = 3, // 提现金额充值
27 | SABER_NO_4 = 4, // 补贴
28 | SABER_NO_5 = 5, // 纠错补贴
29 | SABER_NO_8 = 8, // 提现
30 | SABER_NO_12 = 12, // 无卡充值
31 | SABER_NO_13 = 13, // 补卡充值
32 | SABER_NO_16 = 16, // 补贴清零
33 | SABER_NO_31 = 31, // 金额消费纠错
34 | SABER_NO_32 = 32, // 冲正
35 | SABER_NO_34 = 34, // 日补贴
36 | SABER_NO_35 = 35, // 时段补贴
37 | SABER_NO_36 = 36, // 冻结
38 | SABER_NO_37 = 37, // 解冻
39 | SABER_NO_51 = 51, // 联机记账
40 | SABER_NO_52 = 52, // 联机份记账
41 | SABER_NO_61 = 61 // 银行app充值
42 | }
43 |
44 | @Entity({
45 | name: 'xf_mx'
46 | })
47 | export class Undisguised {
48 | @PrimaryGeneratedColumn('increment', {
49 | name: 'xh'
50 | })
51 | id?: number
52 |
53 | @Column({
54 | name: 'mould',
55 | type: 'int',
56 | default: Mould.SWIM,
57 | comment: '消费模式,0:现金模式 1:计份模式'
58 | })
59 | mould?: Mould
60 |
61 | @Column({
62 | name: 'lx',
63 | type: 'int',
64 | default: RecordType.SABER_NO_61,
65 | comment: '记录类型'
66 | })
67 | recordType?: RecordType
68 |
69 | @Column({
70 | name: 'type',
71 | type: 'int',
72 | comment: '记录来源',
73 | default: RecordSource.DOG
74 | })
75 | recordSource?: RecordSource
76 |
77 | @Column({
78 | name: 'jl_bh',
79 | type: 'varchar',
80 | default: '00000000',
81 | comment: '记录编号'
82 | })
83 | recordNo?: string
84 |
85 | @Column({
86 | name: 'jl_count',
87 | type: 'int',
88 | comment: '修改次数'
89 | })
90 | modifyCount?: number
91 |
92 | @Column({
93 | name: 'user_serial',
94 | type: 'bigint',
95 | comment: '用户id'
96 | })
97 | userSerial?: number
98 |
99 | @Column({
100 | name: 'dep_serial',
101 | type: 'bigint',
102 | comment: '部门id'
103 | })
104 | depSerial?: number
105 |
106 | @Column({
107 | name: 'rq',
108 | type: 'datetime',
109 | comment: '消费日期'
110 | })
111 | createdDate?: Date
112 |
113 | @Column({
114 | name: 'jl_sj',
115 | type: 'datetime',
116 | comment: '记录时间'
117 | })
118 | recordTime?: Date
119 |
120 | @Column({
121 | name: 'dev_serial',
122 | type: 'char',
123 | default: '0000000',
124 | comment: '设备id'
125 | })
126 | devSerial?: string
127 |
128 | @Column({
129 | name: 'old_money',
130 | type: 'money',
131 | comment: '交易前总余额(分)'
132 | })
133 | preTotalBalance?: number
134 |
135 | @Column({
136 | name: 'new_money',
137 | type: 'money',
138 | default: 0,
139 | comment: '实收金额金额(分)。鬼知道怎么用!默认添0就行了!'
140 | })
141 | retardedField?: number
142 |
143 | @Column({
144 | name: 'new_add',
145 | type: 'money',
146 | default: 0,
147 | comment: '充值金额(分)'
148 | })
149 | rechargeAmount?: number
150 |
151 | @Column({
152 | name: 'old_add',
153 | type: 'money',
154 | default: 0,
155 | comment: '充值前金额(分)'
156 | })
157 | preRechargeAmount?: number
158 |
159 | @Column({
160 | name: 'old_sub',
161 | type: 'money',
162 | comment: '补贴交易前余额(分)'
163 | })
164 | preSubsidyBalance?: number
165 |
166 | @Column({
167 | name: 'new_sub',
168 | type: 'money',
169 | default: 0,
170 | comment: '消费/充值补贴(分)'
171 | })
172 | subsidy?: number
173 |
174 | @Column({
175 | name: 'new_del',
176 | type: 'int',
177 | comment: '提现的金额',
178 | default: 0
179 | })
180 | withdrawAmount?: number
181 |
182 | @Column({
183 | name: 'del_count',
184 | type: 'int',
185 | comment: '提现次数',
186 | default: 0
187 | })
188 | withdrawCount?: number
189 |
190 | @Column({
191 | name: 'add_count',
192 | type: 'int',
193 | default: 1,
194 | comment: '充值次数。呵呵呵呵呵呵'
195 | })
196 | rechargeCount?: number
197 |
198 | @Column({
199 | name: 'save_add',
200 | type: 'money',
201 | comment: 'new_add + old_add'
202 | })
203 | currentAmount?: number
204 |
205 | @Column({
206 | name: 'save_sub',
207 | type: 'money',
208 | comment: 'new_sub + old_sub'
209 | })
210 | currentSubsidy?: number
211 |
212 | @Column({
213 | name: 'save_money',
214 | type: 'money',
215 | comment: 'new_add + old_money'
216 | })
217 | currentTotalBalance?: number
218 |
219 | @Column({
220 | name: 'new_each',
221 | type: 'money',
222 | default: 0,
223 | comment: '按份消费的金额'
224 | })
225 | eachAmount?: number
226 |
227 | @Column({
228 | name: 'new_zero',
229 | type: 'money',
230 | default: 0,
231 | comment: '补贴清零的金额'
232 | })
233 | zeroAmount?: number
234 |
235 | @Column({
236 | name: 'del_zero',
237 | type: 'int',
238 | default: 0,
239 | comment: '补贴清零的次数'
240 | })
241 | zeroCount?: number
242 |
243 | @Column({
244 | name: 'new_edit',
245 | type: 'money',
246 | default: 0,
247 | comment: '充正的金额'
248 | })
249 | editAmount?: number
250 |
251 | @Column({
252 | name: 'del_edit',
253 | type: 'int',
254 | default: 0,
255 | comment: '充正的次数'
256 | })
257 | editCount?: number
258 |
259 | @Column({
260 | name: 'dev_time_no',
261 | type: 'char',
262 | comment: '设备时段编号',
263 | default: '0000000000000000'
264 | })
265 | devTimeNo?: string
266 |
267 | @Column({
268 | name: 'time_no',
269 | type: 'char',
270 | comment: '个人时段编号',
271 | default: '0000000000000000'
272 | })
273 | userTimeNo?: string
274 |
275 | @Column({
276 | name: 'sj',
277 | type: 'datetime',
278 | comment: '修改时间'
279 | })
280 | updatedDate?: Date
281 |
282 | @Column({
283 | name: 'gly_no',
284 | type: 'nvarchar',
285 | length: 50,
286 | nullable: true,
287 | default: ADMIN_NO,
288 | comment: '操作人员编号'
289 | })
290 | operatorCode?: string
291 |
292 | @Column({
293 | type: 'varchar',
294 | length: 50,
295 | comment: '交易流水号'
296 | })
297 | tranno?: string
298 |
299 | @BeforeInsert()
300 | computedCurrentAmount() {
301 | this.currentAmount = (this.preRechargeAmount || 0) + (this.rechargeAmount || 0)
302 | }
303 |
304 | @BeforeInsert()
305 | computedCurrentSubsidy() {
306 | this.currentSubsidy = (this.preSubsidyBalance || 0) + (this.subsidy || 0)
307 | }
308 |
309 | @BeforeInsert()
310 | computedCurrentTotalBalance() {
311 | this.currentTotalBalance = (this.preTotalBalance || 0) + (this.rechargeAmount || 0)
312 | }
313 | }
314 |
--------------------------------------------------------------------------------
/src/modules/account/recharge/recharge.controller.ts:
--------------------------------------------------------------------------------
1 | import { Controller, Body, Post, Inject } from '@nestjs/common'
2 | import { ApiUseTags, ApiOperation } from '@nestjs/swagger'
3 | import { RechargeParamsDto } from './recharge.dto'
4 | import { RechargeService } from './recharge.service'
5 |
6 | @ApiUseTags('账户充值服务')
7 | @Controller('account/recharge')
8 | export class RechargeController {
9 | constructor(
10 | @Inject(RechargeService)
11 | private readonly rechargeService: RechargeService
12 | ) {}
13 |
14 | @Post()
15 | @ApiOperation({ title: '人员账户充值' })
16 | async recharge(@Body() body: RechargeParamsDto) {
17 | return await this.rechargeService.recharge(body)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/modules/account/recharge/recharge.dto.ts:
--------------------------------------------------------------------------------
1 | import { Max, Min, IsInt, IsNumber, MaxLength } from 'class-validator'
2 | import { Type } from 'class-transformer'
3 | import { ApiModelProperty } from '@nestjs/swagger'
4 |
5 | export const enum RechargeType {
6 | MONEY = 'money',
7 | SUBSIDY = 'subsidy'
8 | }
9 |
10 | export class RechargeParamsDto {
11 | @ApiModelProperty({
12 | required: true,
13 | enum: [RechargeType.MONEY, RechargeType.SUBSIDY],
14 | description: '充值类型:金额,补贴'
15 | })
16 | readonly type: RechargeType
17 |
18 | @ApiModelProperty({
19 | required: true,
20 | description: '充值金额,单位(元)'
21 | })
22 | @IsNumber({ allowNaN: false }, { message: '错误的充值金额' })
23 | @Max(100000, { message: '充值金额最多不能超过10万' })
24 | @Min(1, { message: '充值金额不能小于1元' })
25 | @Type(() => Number)
26 | readonly amount: number
27 |
28 | @ApiModelProperty({
29 | required: true,
30 | description: '需要充值的用户id'
31 | })
32 | @IsInt({ message: '错误的用户id' })
33 | @Type(() => Number)
34 | readonly userId: number
35 |
36 | @ApiModelProperty({
37 | required: true,
38 | description: '交易流水号'
39 | })
40 | @MaxLength(50, {
41 | message: '交易流水号最多不能超过50个字符'
42 | })
43 | readonly tranno?: string
44 | }
45 |
--------------------------------------------------------------------------------
/src/modules/account/recharge/recharge.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common'
2 | import { TypeOrmModule } from '@nestjs/typeorm'
3 | import { RechargeController } from '@modules/account/recharge/recharge.controller'
4 | import { RechargeService } from '@modules/account/recharge/recharge.service'
5 | import { Strengthen } from '@models/strengthen.model'
6 | import { Lifeblood } from '@models/lifeblood.model'
7 | import { AnotherLifeblood } from '@models/anothor_lifeblood.model'
8 | import { Naked } from '@models/naked.model'
9 | import { Undisguised } from '@models/undisguised.model'
10 | import { Monster } from '@models/monster.model'
11 | import { Assist } from '@models/assist.model'
12 |
13 | @Module({
14 | imports: [
15 | TypeOrmModule.forFeature([Strengthen, Lifeblood, Naked, Undisguised, AnotherLifeblood, Monster, Assist])
16 | ],
17 | controllers: [RechargeController],
18 | providers: [RechargeService]
19 | })
20 | export class RechargeModule { }
21 |
--------------------------------------------------------------------------------
/src/modules/account/recharge/recharge.service.ts:
--------------------------------------------------------------------------------
1 | import { Logger } from 'winston'
2 | import { Injectable, Inject } from '@nestjs/common'
3 | import { InjectRepository } from '@nestjs/typeorm'
4 | import { ADMIN_NO } from '@constants/system.constant'
5 | import { Repository, Transaction, TransactionRepository } from 'typeorm'
6 | import { Strengthen, StrengthenStatus, RechargeMode, RechargeType as RechargeModelType } from '@models/strengthen.model'
7 | import { Lifeblood } from '@models/lifeblood.model'
8 | import { AnotherLifeblood } from '@models/anothor_lifeblood.model'
9 | import { Naked, Mould, RecordType, Status, RecordSource } from '@models/naked.model'
10 | import { Undisguised, RecordType as UndisRecordType } from '@models/undisguised.model'
11 | import { Monster } from '@models/monster.model'
12 | import { Assist, SubsidyMode, SubsidyStatus, IsCleanUp, RechargeType as AssistRechargeType } from '@models/assist.model'
13 | import { BaseService, Service } from '@processors/base/base.service'
14 | import { PlatformError } from '@exceptions/platform.error'
15 | import { RechargeParamsDto, RechargeType } from './recharge.dto'
16 | import { USER_ACCOUNT_NOT_FOUND_ERROR } from '@constants/error/recharge.constant'
17 |
18 | /**
19 | * 注释是不可能写的,
20 | * 它不配拥有注释
21 | */
22 | @Injectable()
23 | export class RechargeService extends BaseService {
24 | constructor(
25 | @Inject('winston') private logger: Logger,
26 | @InjectRepository(Strengthen) private strengthenRepo: Repository,
27 | @InjectRepository(Lifeblood) private lifebloodRepo: Repository,
28 | @InjectRepository(AnotherLifeblood) private anotherlifebloodRepo: Repository,
29 | @InjectRepository(Naked) private nakedRepo: Repository,
30 | @InjectRepository(Undisguised) private undisguisedRepo: Repository,
31 | @InjectRepository(Monster) private monsterRepo: Repository,
32 | @InjectRepository(Assist) private assistRepo: Repository
33 | ) {
34 | super()
35 | }
36 | private strengthenService: Service
37 | private lifebloodService: Service
38 | private anotherlifebloodService: Service
39 | private nakedService: Service
40 | private undisguisedService: Service
41 | private monsterService: Service
42 | private assistService: Service
43 |
44 | @Transaction()
45 | async recharge(
46 | body: RechargeParamsDto,
47 | @TransactionRepository(Strengthen) strengthenRepo?: Repository,
48 | @TransactionRepository(Lifeblood) lifebloodRepo?: Repository,
49 | @TransactionRepository(AnotherLifeblood) anotherlifebloodRepo?: Repository,
50 | @TransactionRepository(Naked) nakedRepo?: Repository,
51 | @TransactionRepository(Undisguised) undisguisedRepo?: Repository,
52 | @TransactionRepository(Monster) monsterRepo?: Repository,
53 | @TransactionRepository(Assist) assistRepo?: Repository
54 | ) {
55 | this.strengthenService = this.getService(strengthenRepo || this.strengthenRepo)
56 | this.lifebloodService = this.getService(lifebloodRepo || this.lifebloodRepo)
57 | this.anotherlifebloodService = this.getService(
58 | anotherlifebloodRepo || this.anotherlifebloodRepo
59 | )
60 | this.nakedService = this.getService(nakedRepo || this.nakedRepo)
61 | this.undisguisedService = this.getService(undisguisedRepo || this.undisguisedRepo)
62 | this.monsterService = this.getService(monsterRepo || this.monsterRepo)
63 | this.assistService = this.getService(assistRepo || this.assistRepo)
64 |
65 | const [userinfo, lifeblood, anotherLifeblood] = await Promise.all([
66 | await this.monsterService.findOne({
67 | id: body.userId
68 | }),
69 | await this.lifebloodService.findOne({
70 | userSerial: body.userId
71 | }),
72 | await this.anotherlifebloodService.findOne({
73 | userSerial: body.userId
74 | })
75 | ])
76 | if (!lifeblood || !anotherLifeblood || !userinfo) {
77 | throw new PlatformError(USER_ACCOUNT_NOT_FOUND_ERROR)
78 | }
79 | try {
80 | switch (body.type) {
81 | case RechargeType.MONEY:
82 | return await this.rechargeMoney(body, userinfo, lifeblood, anotherLifeblood)
83 | case RechargeType.SUBSIDY:
84 | return await this.rechargeSubsidy(body, userinfo, lifeblood, anotherLifeblood)
85 | default:
86 | }
87 | } catch (err) {
88 | this.logger.error(err)
89 | throw err
90 | }
91 | }
92 |
93 | async rechargeMoney(
94 | { tranno, userId, amount }: RechargeParamsDto,
95 | userinfo: Monster,
96 | lifeblood: Lifeblood,
97 | anotherLifeblood: AnotherLifeblood
98 | ) {
99 | const rechargeAmount = amount * 100
100 | const totalBalance = (Number(lifeblood.totalBalance) || 0) + rechargeAmount
101 | const moneyBalance = (Number(lifeblood.moneyBalance) || 0) + rechargeAmount
102 | const accAmont = (Number(anotherLifeblood.accAmont) || 0) + rechargeAmount
103 | return await Promise.all([
104 | await this.strengthenService.create({
105 | tranno,
106 | commission: 0,
107 | createdDate: new Date(),
108 | updatedDate: new Date(),
109 | status: StrengthenStatus.SUCCESS,
110 | operatorCode: ADMIN_NO,
111 | userSerial: userId,
112 | rechargeType: RechargeModelType.HERO,
113 | rechargeMode: RechargeMode.PENTA_KILL,
114 | inputMoney: rechargeAmount,
115 | realMoney: rechargeAmount,
116 | }),
117 | await this.lifebloodService.update(
118 | {
119 | totalBalance,
120 | moneyBalance,
121 | updatedDate: new Date(),
122 | },
123 | { userSerial: userId }
124 | ),
125 | await this.anotherlifebloodService.update(
126 | {
127 | totalBalance,
128 | moneyBalance,
129 | accAmont,
130 | updatedDate: new Date(),
131 | },
132 | { userSerial: userId }
133 | ),
134 | await this.nakedService.create({
135 | tranno,
136 | recordNo: '0',
137 | devSerial: '0000000',
138 | samSerial: '0',
139 | devTimeNo: '0000000000000000',
140 | userTimeNo: '0000000000000000',
141 | subsidy: 0,
142 | preCopiesNum: 0,
143 | unitPrice: 0,
144 | depSerial: Number(userinfo.depNo),
145 | isDelete: 0,
146 | operatorCode: ADMIN_NO,
147 | amountReceivable: 0,
148 | discountRate: 100,
149 | createdDate: new Date(),
150 | updatedDate: new Date(),
151 | mould: Mould.SWIM,
152 | status: Status.GENUINE,
153 | amount: rechargeAmount,
154 | modifyCount: lifeblood.modifyCount,
155 | recordSource: RecordSource.TONY,
156 | recordType: RecordType.SABER_NO_61,
157 | userSerial: userId,
158 | copiesNum: lifeblood.totalCopies,
159 | preSubsidyBalance: lifeblood.subsidyBalance,
160 | preTotalBalance: lifeblood.totalBalance
161 | }),
162 | await this.undisguisedService.create({
163 | tranno,
164 | rechargeAmount,
165 | subsidy: 0,
166 | retardedField: 0,
167 | withdrawAmount: 0,
168 | withdrawCount: 0,
169 | eachAmount: 0,
170 | zeroAmount: 0,
171 | zeroCount: 0,
172 | editAmount: 0,
173 | editCount: 0,
174 | recordNo: '0',
175 | devSerial: '0000000',
176 | devTimeNo: '0000000000000000',
177 | userTimeNo: '0000000000000000',
178 | userSerial: userId,
179 | recordTime: new Date(),
180 | createdDate: new Date(),
181 | updatedDate: new Date(),
182 | depSerial: Number(userinfo.depNo),
183 | operatorCode: ADMIN_NO,
184 | modifyCount: lifeblood.modifyCount,
185 | recordType: UndisRecordType.SABER_NO_61,
186 | preSubsidyBalance: lifeblood.subsidyBalance,
187 | preRechargeAmount: anotherLifeblood.moneyBalance,
188 | preTotalBalance: lifeblood.totalBalance
189 | })
190 | ])
191 | }
192 |
193 | async rechargeSubsidy(
194 | { tranno, userId, amount }: RechargeParamsDto,
195 | userinfo: Monster,
196 | lifeblood: Lifeblood,
197 | anotherLifeblood: AnotherLifeblood
198 | ) {
199 | const rechargeAmount = amount * 100
200 | const totalBalance = (Number(lifeblood.totalBalance) || 0) + rechargeAmount
201 | const subsidyBalance = (Number(lifeblood.subsidyBalance) || 0) + rechargeAmount
202 | const accSubsidy = (Number(anotherLifeblood.accSubsidy) || 0) + rechargeAmount
203 | const maxOrderAssist = await this.assistService.find({
204 | limit: 1,
205 | where: { userSerial: userId },
206 | orderBy: 'order_desc'
207 | })
208 | const order = maxOrderAssist.length > 0 ? Number(maxOrderAssist[0].order) + 1 : 1
209 | const newAssist = await this.assistService.create({
210 | order,
211 | tranno,
212 | subBalance: 0,
213 | createdDate: new Date(),
214 | updatedDate: new Date(),
215 | rechargeType: AssistRechargeType.HERO,
216 | subAmount: rechargeAmount,
217 | operatorCode: ADMIN_NO,
218 | userSerial: userId,
219 | isCleanUp: IsCleanUp.NO_FUCK,
220 | status: SubsidyStatus.SUCCESS,
221 | subsidyMode: SubsidyMode.MISAKA_NO_8,
222 | });
223 | return await Promise.all([
224 | await this.lifebloodService.update(
225 | {
226 | totalBalance,
227 | subsidyBalance,
228 | updatedDate: new Date(),
229 | },
230 | { userSerial: userId }
231 | ),
232 | await this.anotherlifebloodService.update(
233 | {
234 | totalBalance,
235 | subsidyBalance,
236 | accSubsidy,
237 | updatedDate: new Date(),
238 | },
239 | { userSerial: userId }
240 | ),
241 | await this.nakedService.create({
242 | tranno,
243 | recordNo: '0',
244 | devSerial: '0000000',
245 | samSerial: '0',
246 | devTimeNo: '0000000000000000',
247 | userTimeNo: '0000000000000000',
248 | preCopiesNum: 0,
249 | unitPrice: 0,
250 | depSerial: Number(userinfo.depNo),
251 | isDelete: 0,
252 | operatorCode: ADMIN_NO,
253 | amountReceivable: 0,
254 | discountRate: 100,
255 | createdDate: new Date(),
256 | updatedDate: new Date(),
257 | mould: Mould.SWIM,
258 | status: Status.GENUINE,
259 | amount: 0,
260 | assistId: newAssist.id,
261 | subsidy: rechargeAmount,
262 | modifyCount: lifeblood.modifyCount,
263 | recordSource: RecordSource.TONY,
264 | recordType: RecordType.SABER_NO_61,
265 | userSerial: userId,
266 | copiesNum: lifeblood.totalCopies,
267 | preSubsidyBalance: lifeblood.subsidyBalance,
268 | preTotalBalance: lifeblood.totalBalance
269 | }),
270 | await this.undisguisedService.create({
271 | tranno,
272 | rechargeAmount: 0,
273 | retardedField: 0,
274 | withdrawAmount: 0,
275 | withdrawCount: 0,
276 | eachAmount: 0,
277 | zeroAmount: 0,
278 | zeroCount: 0,
279 | editAmount: 0,
280 | editCount: 0,
281 | recordNo: '0',
282 | devSerial: '0000000',
283 | devTimeNo: '0000000000000000',
284 | userTimeNo: '0000000000000000',
285 | userSerial: userId,
286 | recordTime: new Date(),
287 | createdDate: new Date(),
288 | updatedDate: new Date(),
289 | depSerial: Number(userinfo.depNo),
290 | subsidy: rechargeAmount,
291 | operatorCode: ADMIN_NO,
292 | modifyCount: lifeblood.modifyCount,
293 | recordType: UndisRecordType.SABER_NO_61,
294 | preSubsidyBalance: lifeblood.subsidyBalance,
295 | preRechargeAmount: anotherLifeblood.moneyBalance,
296 | preTotalBalance: lifeblood.totalBalance
297 | })
298 | ])
299 | }
300 | }
301 |
--------------------------------------------------------------------------------
/src/modules/app/app.controller.ts:
--------------------------------------------------------------------------------
1 | import { Get, Controller } from '@nestjs/common'
2 | import { ApiExcludeEndpoint } from '@nestjs/swagger'
3 | import { HttpCache } from '@decorators/cache.decorator'
4 | import { HOME_PAGE_CACHE } from '@constants/cache.constant'
5 |
6 | @Controller()
7 | export class AppController {
8 | @Get()
9 | @ApiExcludeEndpoint()
10 | @HttpCache(HOME_PAGE_CACHE, 60 * 60)
11 | root(): string {
12 | return 'hello world!'
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/modules/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { Module, MiddlewareConsumer, RequestMethod } from '@nestjs/common'
2 | import { WinstonModule } from 'nest-winston'
3 | import { APP_INTERCEPTOR } from '@nestjs/core'
4 | import { TypeOrmModule } from '@nestjs/typeorm'
5 | import config from '@config'
6 |
7 | // 首页控制器
8 | import { AppController } from './app.controller'
9 | // 拦截器
10 | import { HttpCacheInterceptor } from '@interceptors/cache.interceptor'
11 | // 中间件
12 | import { CorsMiddleware } from '@middlewares/cors.middleware'
13 | import { OriginMiddleware } from '@middlewares/origin.middleware'
14 | // 公共模块
15 | import { HttpCacheModule } from '@processors/cache/cache.module'
16 | import { HelperModule } from '@processors/helper/helper.module'
17 | // 业务模块
18 | import { AuthorizeModule } from '@modules/authorize/authorize.module'
19 | import { RechargeModule } from '@modules/account/recharge/recharge.module'
20 |
21 | @Module({
22 | imports: [
23 | WinstonModule.forRoot(config.winston),
24 | TypeOrmModule.forRoot({ ...config.typeOrm }),
25 | HttpCacheModule,
26 | HelperModule,
27 | AuthorizeModule,
28 | RechargeModule
29 | ],
30 | controllers: [AppController],
31 | providers: [
32 | {
33 | provide: APP_INTERCEPTOR,
34 | useClass: HttpCacheInterceptor
35 | }
36 | ]
37 | })
38 | export class AppModule {
39 | configure(consumer: MiddlewareConsumer) {
40 | consumer.apply(CorsMiddleware, OriginMiddleware).forRoutes({
41 | path: '(.*)?',
42 | method: RequestMethod.ALL
43 | })
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/modules/authorize/authorize.controller.ts:
--------------------------------------------------------------------------------
1 | import { Controller, Inject, Query, Get } from '@nestjs/common'
2 | import { ApiUseTags, ApiOperation } from '@nestjs/swagger'
3 | import { RsaService } from '@processors/helper/crypto.serivce'
4 | import { ISecretKeys } from '@processors/helper/crypto.serivce'
5 | import { RsaOptionsDto } from './authorize.dto'
6 |
7 | @ApiUseTags('授权认证')
8 | @Controller('authorize')
9 | export class AuthorizeController {
10 | constructor(@Inject(RsaService) private rsaService: RsaService) {}
11 |
12 | @Get('gen_secret_key')
13 | @ApiOperation({ title: '生成RSA公钥和私钥' })
14 | genPubKey(@Query() { pkcsSize = 128, pkcsType = 'pkcs1' }: RsaOptionsDto): ISecretKeys {
15 | return this.rsaService
16 | .setPkcsType(pkcsType)
17 | .setPkcsSize(pkcsSize)
18 | .genSecretKeys()
19 | }
20 |
21 | @Get('gen_temporary_token')
22 | @ApiOperation({ title: '生成临时token' })
23 | genTemporaryToken(@Query() { pkcsSize = 128, pkcsType = 'pkcs1' }: RsaOptionsDto): string {
24 | const { privateKey } = this.rsaService
25 | .setPkcsType(pkcsType)
26 | .setPkcsSize(pkcsSize)
27 | .genSecretKeys()
28 | // 获取当前的时间戳
29 | const timestamp = String(new Date().getTime())
30 | // 时间戳倒序
31 | const reverseTimestamp = timestamp
32 | .split('')
33 | .reverse()
34 | .join('')
35 | // 私钥加密
36 | return this.rsaService.priKeyEncrypt(privateKey, reverseTimestamp)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/modules/authorize/authorize.dto.ts:
--------------------------------------------------------------------------------
1 | import { Max, IsInt, Min } from 'class-validator'
2 | import { Type } from 'class-transformer'
3 | import { ApiModelProperty } from '@nestjs/swagger'
4 |
5 | export class RsaOptionsDto {
6 | @ApiModelProperty({
7 | enum: ['pkcs1', 'pkcs8'],
8 | description: '加密类型'
9 | })
10 | readonly pkcsType?: string
11 |
12 | @ApiModelProperty({
13 | default: 128,
14 | description: '密钥长度'
15 | })
16 | @IsInt({ message: '必须是整数' })
17 | @Max(512, { message: '大小不能超过512' })
18 | @Min(64, { message: '大小不能少于64' })
19 | @Type(() => Number)
20 | readonly pkcsSize?: number
21 | }
22 |
--------------------------------------------------------------------------------
/src/modules/authorize/authorize.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common'
2 | import { AuthorizeController } from '@modules/authorize/authorize.controller'
3 |
4 | @Module({
5 | controllers: [AuthorizeController]
6 | })
7 | export class AuthorizeModule {}
8 |
--------------------------------------------------------------------------------
/src/pipes/validation.pipe.ts:
--------------------------------------------------------------------------------
1 | import { validate } from 'class-validator'
2 | import { plainToClass } from 'class-transformer'
3 | import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common'
4 | import { ValidationError } from '@exceptions/validation.error'
5 | import { VALIDATION_ERROR } from '@constants/error/general.constant'
6 |
7 | /**
8 | * @class ValidationPipe
9 | * @classdesc 验证所有使用 class-validator 的地方的 class 模型
10 | */
11 | @Injectable()
12 | export class ValidationPipe implements PipeTransform {
13 | async transform(value: any, { metatype }: ArgumentMetadata) {
14 | if (!metatype || !this.toValidate(metatype)) {
15 | return value
16 | }
17 | const object = plainToClass(metatype, value)
18 | const errors = await validate(object)
19 | if (errors.length > 0) {
20 | const errorMessage = errors.map(error => Object.values(error.constraints).join(';')).join(';')
21 | throw new ValidationError({
22 | ...VALIDATION_ERROR,
23 | error: errorMessage
24 | })
25 | }
26 | return value
27 | }
28 |
29 | private toValidate(metatype: any): boolean {
30 | const types = [String, Boolean, Number, Array, Object]
31 | return !types.find(type => metatype === type)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/processors/base/base.service.ts:
--------------------------------------------------------------------------------
1 | import * as assert from 'assert'
2 | import { validate } from 'class-validator'
3 | import {
4 | DeepPartial,
5 | FindManyOptions,
6 | FindOperator,
7 | Repository,
8 | Equal,
9 | In,
10 | IsNull,
11 | LessThan,
12 | MoreThan,
13 | Not,
14 | Raw
15 | } from 'typeorm'
16 |
17 | export interface IFindParam {
18 | where?: any
19 | orderBy?: any
20 | limit?: number
21 | offset?: number
22 | fields?: string[]
23 | }
24 |
25 | export class BaseService {
26 | static serviceMap: Map> = new Map()
27 |
28 | constructor(...args) {
29 | const self = this
30 | const serviceClass = this.constructor
31 | const injectedServices = Reflect.getMetadata('injected_services', serviceClass) || []
32 | injectedServices.forEach((value, key) => {
33 | args.forEach(obj => {
34 | if (obj instanceof Repository && obj.metadata.target === value) {
35 | self[key] = this.getService(obj)
36 | }
37 | })
38 | assert(self[key], `未找到${key}属性对应的Repository实例,请在构造函数中注入此实例`)
39 | })
40 | }
41 |
42 | static getService(repository: Repository): Service {
43 | const className = repository.metadata.name
44 | if (this.serviceMap.has(className)) {
45 | const service = this.serviceMap.get(className)
46 | if (service && service.repository === repository) {
47 | return service
48 | }
49 | }
50 | const newService = new Service(repository)
51 | this.serviceMap.set(className, newService)
52 | return newService
53 | }
54 |
55 | getService(repository: Repository): Service {
56 | return BaseService.getService(repository)
57 | }
58 | }
59 |
60 | export class Service {
61 | constructor(public readonly repository: Repository) { }
62 |
63 | private async findOption({ where, limit, offset, fields, orderBy }: IFindParam = {}): Promise<
64 | any
65 | > {
66 | const findOptions: FindManyOptions = {}
67 |
68 | if (limit) {
69 | findOptions.take = limit
70 | }
71 | if (offset) {
72 | findOptions.skip = offset
73 | }
74 | if (fields) {
75 | findOptions.select = fields
76 | }
77 | if (orderBy) {
78 | const parts = orderBy.toString().split('_')
79 | const attr = parts[0]
80 | const direction: 'ASC' | 'DESC' = parts[1] as 'ASC' | 'DESC'
81 | findOptions.order = {
82 | [attr]: direction
83 | }
84 | }
85 | findOptions.where = this.processWhereOptions(where)
86 | return findOptions
87 | }
88 |
89 | async find({ where, limit, offset, fields, orderBy }: IFindParam = {}): Promise {
90 | const findOptions: any = await this.findOption({ where, limit, offset, fields, orderBy })
91 | return await this.repository.find(findOptions)
92 | }
93 |
94 | async count({ where }: IFindParam = {}): Promise {
95 | const findOptions: any = await this.findOption({ where })
96 | return await this.repository.count(findOptions)
97 | }
98 |
99 | async findAndCount({ where, limit, offset, fields, orderBy }: IFindParam = {}): Promise<{
100 | rows: E[]
101 | count: number
102 | }> {
103 | const findOptions: any = await this.findOption({ where, limit, offset, fields, orderBy })
104 | const [rows, count] = await this.repository.findAndCount(findOptions)
105 | return { rows, count }
106 | }
107 |
108 | async findOne(where, fields?: string[], orderBy?: string): Promise {
109 | const items = await this.find({ where, fields, orderBy })
110 | if (!items.length) {
111 | return null
112 | } else if (items.length > 1) {
113 | throw new Error(
114 | `Found ${items.length} ${this.repository.metadata.name}s where ${JSON.stringify(where)}`
115 | )
116 | }
117 | return items[0]
118 | }
119 |
120 | async create(data: DeepPartial): Promise {
121 | const results = await this.repository.create([data])
122 | const obj = results[0]
123 |
124 | const errors = await validate(obj, { skipMissingProperties: true })
125 | if (errors.length) {
126 | throw new Error(JSON.stringify(errors))
127 | }
128 | return await this.repository.save(obj, { reload: true })
129 | }
130 |
131 | async createMany(data: Array>): Promise {
132 | data = data.map(item => {
133 | return { ...item }
134 | })
135 |
136 | const results = await this.repository.create(data)
137 |
138 | results.forEach(async obj => {
139 | const errors = await validate(obj, { skipMissingProperties: true })
140 | if (errors.length) {
141 | throw new Error(JSON.stringify(errors))
142 | }
143 | })
144 |
145 | return await this.repository.save(results, { reload: true })
146 | }
147 |
148 | async update(data: DeepPartial, where): Promise {
149 | const found = await this.findOne(where)
150 | if (!found) {
151 | return false
152 | }
153 | const merged = this.repository.merge(found, data)
154 |
155 | const errors = await validate(merged, { skipMissingProperties: true })
156 | if (errors.length) {
157 | throw new Error(JSON.stringify(errors))
158 | }
159 | return await this.repository.save(merged)
160 | }
161 |
162 | processWhereOptions(where: W) {
163 | if (Array.isArray(where)) {
164 | const whereOptions: Array<{ [key: string]: FindOperator }> = []
165 | Object.keys(where).forEach(k => {
166 | const options: any = {}
167 | for (const index in where[k]) {
168 | const key = index as keyof W
169 | if (where[k][key] === undefined) {
170 | continue
171 | }
172 | const [attr, operator] = getFindOperator(String(key), where[k][key])
173 | options[attr] = operator
174 | }
175 | whereOptions.push(options)
176 | })
177 | return whereOptions
178 | } else {
179 | const whereOptions: { [key: string]: FindOperator } = {}
180 | Object.keys(where).forEach(k => {
181 | const key = k as keyof W
182 | if (where[key] !== undefined) {
183 | const [attr, operator] = getFindOperator(String(key), where[key])
184 | whereOptions[attr] = operator
185 | }
186 | })
187 | return whereOptions
188 | }
189 | }
190 | }
191 |
192 | export function getFindOperator(key: string, value: any): [string, FindOperator] {
193 | const parts = key.toString().split('_')
194 | const attr = parts[0]
195 | const operator = parts.length > 1 ? parts[1] : 'eq'
196 |
197 | switch (operator) {
198 | case 'eq':
199 | if (value === null) {
200 | return [attr, IsNull()]
201 | }
202 | return [attr, Equal(value)]
203 | // // Not using "not" yet
204 | // case 'not':
205 | // return [attr, Not(value)];
206 | case 'lt':
207 | return [attr, LessThan(value)]
208 | case 'lte':
209 | return [attr, Not(MoreThan(value))]
210 | case 'gt':
211 | return [attr, MoreThan(value)]
212 | case 'gte':
213 | return [attr, Not(LessThan(value))]
214 | case 'in':
215 | return [attr, In(value)]
216 | case 'contains':
217 | return [attr, Raw(alias => `LOWER(${alias}) LIKE LOWER('%${value}%')`)]
218 | case 'startsWith':
219 | return [attr, Raw(alias => `LOWER(${alias}) LIKE LOWER('${value}%')`)]
220 | case 'endsWith':
221 | return [attr, Raw(alias => `LOWER(${alias}) LIKE LOWER('%${value}')`)]
222 | default:
223 | throw new Error(`Can't find operator ${operator}`)
224 | }
225 | }
226 |
--------------------------------------------------------------------------------
/src/processors/cache/cache.module.ts:
--------------------------------------------------------------------------------
1 | import { CacheModule as NestCacheModule, Global, Module } from '@nestjs/common'
2 | import { CacheConfigService } from './cache.service.config'
3 | import { CacheService } from './cache.service'
4 |
5 | @Global()
6 | @Module({
7 | imports: [
8 | NestCacheModule.registerAsync({
9 | useClass: CacheConfigService,
10 | inject: [CacheConfigService]
11 | })
12 | ],
13 | providers: [CacheConfigService, CacheService],
14 | exports: [NestCacheModule, CacheService]
15 | })
16 | export class HttpCacheModule {}
17 |
--------------------------------------------------------------------------------
/src/processors/cache/cache.service.config.ts:
--------------------------------------------------------------------------------
1 | import * as redisStore from 'cache-manager-redis-store'
2 | import { Logger } from 'winston'
3 | import { CacheModuleOptions, CacheOptionsFactory, Injectable, Inject } from '@nestjs/common'
4 | import config from '@config'
5 |
6 | @Injectable()
7 | export class CacheConfigService implements CacheOptionsFactory {
8 | constructor(@Inject('winston') private readonly logger: Logger) {}
9 |
10 | // 重试策略
11 | public retryStrategy() {
12 | return {
13 | retry_strategy: options => {
14 | if (options.error && options.error.code === 'ECONNREFUSED') {
15 | this.logger.warn('Redis 连接出了问题:', options.error)
16 | return new Error('Redis 服务器拒绝连接')
17 | }
18 | if (options.total_retry_time > 1000 * 60) {
19 | return new Error('重试时间已用完')
20 | }
21 | if (options.attempt > 6) {
22 | return new Error('尝试次数已达极限')
23 | }
24 | return Math.min(options.attempt * 100, 3000)
25 | }
26 | }
27 | }
28 |
29 | // 缓存配置
30 | public createCacheOptions(): CacheModuleOptions {
31 | return { store: redisStore, ...config.redis }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/processors/cache/cache.service.ts:
--------------------------------------------------------------------------------
1 | import { CACHE_MANAGER, Inject, Injectable } from '@nestjs/common'
2 |
3 | // Cache 客户端管理器
4 | export interface ICacheManager {
5 | store: any
6 | get(key: TCacheKey): any
7 | set(key: TCacheKey, value: string, options?: { ttl: number }): any
8 | }
9 |
10 | // 获取器
11 | export type TCacheKey = string
12 | export type TCacheResult = Promise
13 |
14 | // IO 模式通用返回结构
15 | export interface ICacheIoResult {
16 | get(): TCacheResult
17 | update(): TCacheResult
18 | }
19 |
20 | // Promise 模式参数
21 | export interface ICachePromiseOption {
22 | key: TCacheKey
23 | promise(): TCacheResult
24 | }
25 |
26 | // Promise & IO 模式参数
27 | export interface ICachePromiseIoOption extends ICachePromiseOption {
28 | ioMode?: boolean
29 | }
30 |
31 | // Interval & Timeout 超时模式参数
32 | export interface ICacheIntervalTimeoutOption {
33 | error?: number
34 | success?: number
35 | }
36 |
37 | // Interval & Timing 定时模式参数
38 | export interface ICacheIntervalTimingOption {
39 | error: number
40 | schedule: any
41 | }
42 |
43 | // Interval 模式参数
44 | export interface ICacheIntervalOption {
45 | key: TCacheKey
46 | promise(): TCacheResult
47 | timeout?: ICacheIntervalTimeoutOption
48 | timing?: ICacheIntervalTimingOption
49 | }
50 |
51 | // Interval 模式返回类型
52 | export type TCacheIntervalResult = () => TCacheResult
53 |
54 | // Interval & IO 模式参数
55 | export interface ICacheIntervalIOOption extends ICacheIntervalOption {
56 | ioMode?: boolean
57 | }
58 |
59 | /**
60 | * @class CacheService
61 | * @classdesc 承载缓存服务
62 | * @example CacheService.get(CacheKey).then()
63 | * @example CacheService.set(CacheKey).then()
64 | * @example CacheService.promise({ option })()
65 | * @example CacheService.interval({ option })()
66 | */
67 | @Injectable()
68 | export class CacheService {
69 | private cache!: ICacheManager
70 |
71 | constructor(@Inject(CACHE_MANAGER) cache: ICacheManager) {
72 | this.cache = cache
73 | }
74 |
75 | // 客户端是否可用
76 | private get checkCacheServiceAvailable(): boolean {
77 | const client = this.cache.store.getClient()
78 | return client.connected && client.ready
79 | }
80 |
81 | public get(key: TCacheKey): TCacheResult {
82 | if (!this.checkCacheServiceAvailable) {
83 | return Promise.reject('缓存客户端没准备好')
84 | }
85 | return this.cache.get(key)
86 | }
87 |
88 | public set(key: TCacheKey, value: any, options?: { ttl: number }): TCacheResult {
89 | if (!this.checkCacheServiceAvailable) {
90 | return Promise.reject('缓存客户端没准备好')
91 | }
92 | return this.cache.set(key, value, options)
93 | }
94 |
95 | /**
96 | * @function promise
97 | * @description 被动更新 | 双向同步 模式,Promise -> Redis
98 | * @example CacheService.promise({ key: CacheKey, promise() }) -> promise()
99 | * @example CacheService.promise({ key: CacheKey, promise(), ioMode: true }) -> { get: promise(), update: promise() }
100 | */
101 | promise(options: ICachePromiseOption): TCacheResult
102 | promise(options: ICachePromiseIoOption): ICacheIoResult
103 | promise(options) {
104 | const { key, promise, ioMode = false } = options
105 |
106 | // 包装任务
107 | const promiseTask = (resolve, reject) => {
108 | return promise()
109 | .then(data => {
110 | this.set(key, data)
111 | resolve(data)
112 | })
113 | .catch(reject)
114 | }
115 |
116 | // Promise 拦截模式(返回死数据)
117 | const handlePromiseMode = () => {
118 | return new Promise((resolve, reject) => {
119 | this.get(key)
120 | .then(value => {
121 | const isValidValue = value !== null && value !== undefined
122 | isValidValue ? resolve(value) : promiseTask(resolve, reject)
123 | })
124 | .catch(reject)
125 | })
126 | }
127 |
128 | // 双向同步模式(返回获取器和更新器)
129 | const handleIoMode = () => ({
130 | get: handlePromiseMode,
131 | update: () => new Promise(promiseTask)
132 | })
133 | return ioMode ? handleIoMode() : handlePromiseMode()
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/processors/helper/crypto.serivce.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common'
2 | import * as NodeRSA from 'node-rsa'
3 | import * as CryptoJS from 'crypto-js'
4 |
5 | export interface ISecretKeys {
6 | publicKey: string
7 | privateKey: string
8 | }
9 |
10 | @Injectable()
11 | export class RsaService {
12 | private rsaMap: Map = new Map()
13 |
14 | private _pkcsType: string = 'pkcs1'
15 |
16 | private _pkcsSize: number = 128
17 |
18 | /**
19 | * 设置加密类型
20 | * @param pkcsType
21 | */
22 | setPkcsType(pkcsType: string): RsaService {
23 | this._pkcsType = pkcsType
24 | return this
25 | }
26 |
27 | /**
28 | * 设置密钥长度
29 | * @param pkcsSize
30 | */
31 | setPkcsSize(pkcsSize: number): RsaService {
32 | this._pkcsSize = pkcsSize
33 | return this
34 | }
35 |
36 | /**
37 | * 创建rsa实例,并缓存
38 | */
39 | private getInstance(isNew?: boolean) {
40 | if (isNew) {
41 | return new NodeRSA({ b: this._pkcsSize })
42 | }
43 | const key = `${this._pkcsType}_${this._pkcsSize}` // 创建RSA对象,并指定秘钥长度
44 | if (!this.rsaMap.has(key)) {
45 | const rsa = new NodeRSA({ b: this._pkcsSize })
46 | rsa.setOptions({ encryptionScheme: this._pkcsType }) // 指定加密格式
47 | this.rsaMap.set(key, rsa)
48 | }
49 | return this.rsaMap.get(key)
50 | }
51 |
52 | /**
53 | * 生成公钥、私钥
54 | */
55 | public genSecretKeys(isNew: boolean = true): ISecretKeys {
56 | const rsa = this.getInstance(isNew)
57 | return {
58 | publicKey: rsa.exportKey(`${this._pkcsType}-public-pem`),
59 | privateKey: rsa.exportKey(`${this._pkcsType}-private-pem`)
60 | }
61 | }
62 |
63 | /**
64 | * 私钥加密
65 | * @param priKey
66 | * @param data
67 | */
68 | public priKeyEncrypt(priKey: string, data: string): string {
69 | const rsa = this.getInstance()
70 | rsa.importKey(priKey)
71 | return rsa.encryptPrivate(data, 'base64', 'utf8')
72 | }
73 |
74 | /**
75 | * 公钥解密
76 | * @param pubKey
77 | * @param data
78 | */
79 | public pubKeyDecrypt(pubKey: string, data: string): string {
80 | const rsa = this.getInstance()
81 | rsa.importKey(pubKey)
82 | return rsa.decryptPublic(data, 'utf8')
83 | }
84 | }
85 |
86 | @Injectable()
87 | export class CryptoService {
88 | keyStr = 'k;)*(+nmjdsf$#@d'
89 |
90 | encrypt(word) {
91 | const key = CryptoJS.enc.Utf8.parse(this.keyStr)
92 | const srcs = CryptoJS.enc.Utf8.parse(word)
93 | return CryptoJS.AES.encrypt(srcs, key, {
94 | mode: CryptoJS.mode.ECB,
95 | padding: CryptoJS.pad.Pkcs7
96 | }).toString()
97 | }
98 |
99 | decrypt(word) {
100 | const key = CryptoJS.enc.Utf8.parse(this.keyStr)
101 | return CryptoJS.AES.decrypt(word, key, {
102 | mode: CryptoJS.mode.ECB,
103 | padding: CryptoJS.pad.Pkcs7
104 | }).toString(CryptoJS.enc.Utf8)
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/processors/helper/crypto.spec.ts:
--------------------------------------------------------------------------------
1 | import { RsaService } from './crypto.serivce'
2 |
3 | test('ras加密解密', () => {
4 | const service = new RsaService()
5 | const { privateKey, publicKey } = service.genSecretKeys()
6 | const str = 'fuck!!!哟哟哟!!'
7 | const exec = () => {
8 | const encodeStr = service.priKeyEncrypt(privateKey, str)
9 | return service.pubKeyDecrypt(publicKey, encodeStr)
10 | }
11 | expect(exec()).toBe(str)
12 | })
13 |
--------------------------------------------------------------------------------
/src/processors/helper/helper.module.ts:
--------------------------------------------------------------------------------
1 | import { Module, Global } from '@nestjs/common'
2 | import { RsaService } from './crypto.serivce'
3 |
4 | @Global()
5 | @Module({
6 | providers: [RsaService],
7 | exports: [RsaService]
8 | })
9 | export class HelperModule {}
10 |
--------------------------------------------------------------------------------
/src/server.ts:
--------------------------------------------------------------------------------
1 | import * as rateLimit from 'fastify-rate-limit'
2 | import * as helmet from 'fastify-helmet'
3 | import * as compress from 'fastify-compress'
4 | import { NestFactory, Reflector } from '@nestjs/core'
5 | import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify'
6 | import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'
7 | import { AppModule } from '@modules/app/app.module'
8 | import { ValidationPipe } from '@pipes/validation.pipe'
9 | import { HttpExceptionFilter } from '@filters/exception.filter'
10 | import { TransformInterceptor } from '@interceptors/transform.interceptor'
11 | import { ErrorInterceptor } from '@interceptors/error.interceptor'
12 | import { LoggingInterceptor } from '@interceptors/logging.interceptor'
13 | import config from '@config'
14 |
15 | async function bootstrap() {
16 | const urlPrefix = 'api/v1'
17 | const adapter = new FastifyAdapter()
18 | // 安全防护
19 | adapter.register(helmet)
20 | // 压缩请求
21 | adapter.register(compress)
22 | // 限制访问频率,多实例建议走redis
23 | adapter.register(rateLimit, {
24 | max: 100,
25 | timeWindow: 6000 // 一分钟
26 | })
27 | const nestApp = await NestFactory.create(AppModule, adapter)
28 | // 设置全局前缀
29 | nestApp.setGlobalPrefix(urlPrefix)
30 | // 全局使用winston
31 | const nestWinston = nestApp.get('NestWinston')
32 | nestApp.useLogger(nestWinston)
33 | // 捕获全局错误
34 | nestApp.useGlobalFilters(new HttpExceptionFilter(nestWinston.logger))
35 | // class-validator效验
36 | nestApp.useGlobalPipes(new ValidationPipe())
37 | // 添加拦截器
38 | nestApp.useGlobalInterceptors(
39 | new TransformInterceptor(new Reflector()),
40 | new ErrorInterceptor(new Reflector(), nestWinston.logger),
41 | new LoggingInterceptor(nestWinston.logger)
42 | )
43 | // Swagger文档
44 | const options = new DocumentBuilder()
45 | .setBasePath(urlPrefix)
46 | .setTitle('接口文档')
47 | .setDescription('接口文档')
48 | .setVersion('1.0')
49 | .build()
50 | const document = SwaggerModule.createDocument(nestApp, options)
51 | SwaggerModule.setup('/docs', nestApp, document)
52 | // 监听服务端口
53 | await nestApp.listen(config.port)
54 | return nestWinston.logger
55 | }
56 | bootstrap().then(logger => {
57 | logger.info(`url: http://127.0.0.1:${config.port}, env: ${config.env}`)
58 | })
59 |
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": ["node_modules", "test", "**/*spec.ts"]
4 | }
5 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "alwaysStrict": true,
4 | "removeComments": true,
5 | "emitDecoratorMetadata": true,
6 | "experimentalDecorators": true,
7 | "lib": [
8 | "dom",
9 | "es2018"
10 | ],
11 | "module": "commonjs",
12 | "noImplicitAny": false,
13 | "moduleResolution": "node",
14 | "noUnusedLocals": true,
15 | "noUnusedParameters": true,
16 | "strict": true,
17 | "skipLibCheck": true,
18 | "forceConsistentCasingInFileNames": true,
19 | "sourceMap": true,
20 | "target": "es2017",
21 | "charset": "utf8",
22 | "allowJs": false,
23 | "pretty": true,
24 | "noEmitOnError": false,
25 | "allowUnreachableCode": false,
26 | "allowUnusedLabels": false,
27 | "strictPropertyInitialization": false,
28 | "noFallthroughCasesInSwitch": true,
29 | "skipDefaultLibCheck": true,
30 | "importHelpers": true,
31 | "outDir": "./dist",
32 | "baseUrl": "./",
33 | "paths": {
34 | "@src/*": [
35 | "./src/*"
36 | ],
37 | "@config/*": [
38 | "./src/config/*"
39 | ],
40 | "@config": [
41 | "./src/config/main.config.ts"
42 | ],
43 | "@interfaces/*": [
44 | "./src/interfaces/*"
45 | ],
46 | "@filters/*": [
47 | "./src/filters/*"
48 | ],
49 | "@processors/*": [
50 | "./src/processors/*"
51 | ],
52 | "@modules/*": [
53 | "./src/modules/*"
54 | ],
55 | "@interceptors/*": [
56 | "./src/interceptors/*"
57 | ],
58 | "@exceptions/*": [
59 | "./src/exceptions/*"
60 | ],
61 | "@constants/*": [
62 | "./src/constants/*"
63 | ],
64 | "@pipes/*": [
65 | "./src/pipes/*"
66 | ],
67 | "@utils/*": [
68 | "./src/utils/*"
69 | ],
70 | "@transforms/*": [
71 | "./src/transforms/*"
72 | ],
73 | "@decorators/*": [
74 | "./src/decorators/*"
75 | ],
76 | "@middlewares/*": [
77 | "./src/middlewares/*"
78 | ],
79 | "@models/*": [
80 | "./src/models/*"
81 | ]
82 | }
83 | },
84 | "exclude": [
85 | "node_modules"
86 | ],
87 | "include": [
88 | "**/*.ts",
89 | "**/*.tsx"
90 | ]
91 | }
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "error",
3 | "extends": ["tslint:recommended", "tslint-config-prettier"],
4 | "jsRules": {
5 | "no-unused-expression": true
6 | },
7 | "rules": {
8 | "quotemark": [true, "single"],
9 | "member-access": [false],
10 | "ordered-imports": [false],
11 | "max-line-length": [true, 100],
12 | "member-ordering": [false],
13 | "interface-name": [false],
14 | "arrow-parens": false,
15 | "object-literal-sort-keys": false,
16 | "max-classes-per-file": false
17 | },
18 | "rulesDirectory": []
19 | }
20 |
--------------------------------------------------------------------------------