diff --git a/.vscode/settings.json b/.vscode/settings.json index cb4333e..a556f99 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,59 +1,6 @@ { "typescript.tsdk": "./node_modules/typescript/lib", - "volar.tsPlugin": true, - "volar.tsPluginStatus": false, "npm.packageManager": "pnpm", - "editor.tabSize": 2, - "editor.defaultFormatter": "esbenp.prettier-vscode", - "files.eol": "\n", - "search.exclude": { - "**/node_modules": true, - "**/*.log": true, - "**/*.log*": true, - "**/bower_components": true, - "**/dist": true, - "**/elehukouben": true, - "**/.git": true, - "**/.gitignore": true, - "**/.svn": true, - "**/.DS_Store": true, - "**/.idea": true, - "**/.vscode": false, - "**/yarn.lock": true, - "**/tmp": true, - "out": true, - "dist": true, - "node_modules": true, - "CHANGELOG.md": true, - "examples": true, - "res": true, - "screenshots": true, - "yarn-error.log": true, - "**/.yarn": true - }, - "files.exclude": { - "**/.cache": true, - "**/.editorconfig": true, - "**/.eslintcache": true, - "**/bower_components": true, - "**/.idea": true, - "**/tmp": true, - "**/.git": true, - "**/.svn": true, - "**/.hg": true, - "**/CVS": true, - "**/.DS_Store": true - }, - "files.watcherExclude": { - "**/.git/objects/**": true, - "**/.git/subtree-cache/**": true, - "**/.vscode/**": true, - "**/node_modules/**": true, - "**/tmp/**": true, - "**/bower_components/**": true, - "**/dist/**": true, - "**/yarn.lock": true - }, "stylelint.enable": true, "stylelint.validate": [ "css", @@ -63,38 +10,12 @@ "vue", "sass" ], - "path-intellisense.mappings": { - "/@/": "${workspaceRoot}/src" - }, - "[javascriptreact]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[typescript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[typescriptreact]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[html]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[css]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[less]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[scss]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[markdown]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, + "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.codeActionsOnSave": { "source.fixAll.eslint": true, "source.fixAll.stylelint": true }, - "editor.formatOnSave": false, + "editor.formatOnSave": true, "[vue]": { "editor.codeActionsOnSave": { "source.fixAll.eslint": true, @@ -102,7 +23,7 @@ } }, "i18n-ally.localesPaths": [ - "src/locales/lang" + "apps/admin/src/locales/lang" ], "i18n-ally.keystyle": "nested", "i18n-ally.sortKeys": true, @@ -117,68 +38,7 @@ "vue", "react" ], - "cSpell.words": [ - "vben", - "browserslist", - "tailwindcss", - "esnext", - "antv", - "tinymce", - "qrcode", - "sider", - "pinia", - "sider", - "nprogress", - "INTLIFY", - "stylelint", - "esno", - "vitejs", - "sortablejs", - "mockjs", - "codemirror", - "iconify", - "commitlint", - "vditor", - "echarts", - "cropperjs", - "logicflow", - "vueuse", - "zxcvbn", - "lintstagedrc", - "brotli", - "tailwindcss", - "sider", - "pnpm", - "antd" - ], - "MicroPython.executeButton": [ - { - "text": "▶", - "tooltip": "运行", - "alignment": "left", - "command": "extension.executeFile", - "priority": 3.5 - } - ], - "MicroPython.syncButton": [ - { - "text": "$(sync)", - "tooltip": "同步", - "alignment": "left", - "command": "extension.execute", - "priority": 4 - } - ], - // 控制相关文件嵌套展示 - "explorer.fileNesting.enabled": true, - "explorer.fileNesting.expand": false, - "explorer.fileNesting.patterns": { - "*.ts": "$(capture).test.ts, $(capture).test.tsx", - "*.tsx": "$(capture).test.ts, $(capture).test.tsx", - "*.env": "$(capture).env.*", - "CHANGELOG.md": "CHANGELOG*", - "package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,LICENSE,.gitattributes,.gitignore,.gitpod.yml,CNAME,README*,.npmrc,.browserslistrc", - ".eslintrc.js": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.js,.prettierrc.js,.stylelintrc.js" + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" }, - "terminal.integrated.scrollback": 10000 } \ No newline at end of file diff --git a/apps/api/.eslintrc.js b/apps/api/.eslintrc.js index 635af60..f6b834f 100644 --- a/apps/api/.eslintrc.js +++ b/apps/api/.eslintrc.js @@ -1,73 +1,67 @@ module.exports = { parser: '@typescript-eslint/parser', parserOptions: { - project: 'tsconfig.json', - tsconfigRootDir: __dirname, sourceType: 'module', - extraFileExtensions: ['.json'] + tsconfigRootDir: __dirname, }, root: true, env: { node: true, jest: true, }, - plugins: ['@typescript-eslint', 'import', 'unused-imports'], + plugins: ['@typescript-eslint', 'unused-imports'], extends: [ - 'airbnb-base', - 'airbnb-typescript/base', - + 'eslint:recommended', 'plugin:@typescript-eslint/recommended', - 'plugin:prettier/recommended' + 'prettier', + 'plugin:import/recommended', ], rules: { - '@typescript-eslint/interface-name-prefix': 'off', - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/explicit-module-boundary-types': 'off', - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-var-requires': 'off', - '@typescript-eslint/no-inferrable-types': 'off', - '@typescript-eslint/no-shadow': 'off', - '@typescript-eslint/naming-convention': 'off', - - "max-classes-per-file": "off", - "class-methods-use-this": "off", - "no-param-reassign": "off", - - 'unused-imports/no-unused-imports': 1, - 'unused-imports/no-unused-vars': [ - 'error', - { - vars: 'all', - args: 'none', - ignoreRestSiblings: true, - }, - ], - - 'import/extensions': 0, - 'import/prefer-default-export': 0, - 'import/no-cycle': 0, - 'import/no-import-module-exports': 0, - + // import + 'import/named': 'off', + 'import/first': 'error', + 'import/no-mutable-exports': 'error', + 'import/no-unresolved': 'off', + 'import/no-absolute-path': 'off', + 'import/no-default-export': 'off', + 'import/no-cycle': 'off', + 'import/no-import-module-exports': 'off', 'import/order': [ 'error', { - pathGroups: [ + 'pathGroups': [ { pattern: '@/**', group: 'external', position: 'after', }, ], - alphabetize: { order: 'asc', caseInsensitive: false }, + 'alphabetize': { order: 'asc', caseInsensitive: false }, 'newlines-between': 'always-and-inside-groups', - warnOnUnassignedImports: true, + 'warnOnUnassignedImports': true, + }, + ], + + 'unused-imports/no-unused-imports': 'error', + 'unused-imports/no-unused-vars': [ + 'warn', + { + vars: 'all', + varsIgnorePattern: '^_', + args: 'after-used', + argsIgnorePattern: '^_', }, ], - 'no-restricted-syntax': 'off', - 'no-await-in-loop': 'off', + '@typescript-eslint/interface-name-prefix': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-var-requires': 'off', + '@typescript-eslint/no-inferrable-types': 'off', + '@typescript-eslint/no-shadow': 'off', + '@typescript-eslint/naming-convention': 'off', - 'radix': 'off', - 'no-bitwise': 'off' + '@typescript-eslint/no-unused-vars': 'off', }, -}; +} diff --git a/apps/api/.prettierrc b/apps/api/.prettierrc index d01d25a..c5a6d2b 100644 --- a/apps/api/.prettierrc +++ b/apps/api/.prettierrc @@ -2,12 +2,10 @@ "printWidth": 80, "tabWidth": 2, "useTabs": false, - "semi": true, + "semi": false, "singleQuote": true, - "quoteProps": "as-needed", + "quoteProps": "consistent", "bracketSpacing": true, "arrowParens": "always", - "trailingComma": "all", - "nsertPragma": false, - "endOfLine": "auto" + "trailingComma": "all" } diff --git a/apps/api/src/app.module.ts b/apps/api/src/app.module.ts index 4f8d1de..eb17627 100644 --- a/apps/api/src/app.module.ts +++ b/apps/api/src/app.module.ts @@ -1,23 +1,23 @@ -import { Module } from '@nestjs/common'; - -import { ConfigModule } from '@nestjs/config'; -import { APP_FILTER, APP_GUARD } from '@nestjs/core'; - -import * as config from '@/config'; -import { SharedModule } from '@/shared/shared.module'; - -import { AllExceptionsFilter } from './common/filters/any-exception.filter'; - -import { TodoModule } from './modules/todo/todo.module'; -import { AuthModule } from './modules/auth/auth.module'; -import { JwtAuthGuard } from './modules/auth/guards/jwt-auth.guard'; -import { RbacGuard } from './modules/auth/guards/rbac.guard'; -import { HealthModule } from './modules/health/health.module'; -import { SocketModule } from './modules/socket/socket.module'; -import { SystemModule } from './modules/system/system.module'; -import { TasksModule } from './modules/tasks/tasks.module'; -import { ToolsModule } from './modules/tools/tools.module'; -import { DatabaseModule } from './shared/database/database.module'; +import { Module } from '@nestjs/common' + +import { ConfigModule } from '@nestjs/config' +import { APP_FILTER, APP_GUARD } from '@nestjs/core' + +import * as config from '@/config' +import { SharedModule } from '@/shared/shared.module' + +import { AllExceptionsFilter } from './common/filters/any-exception.filter' + +import { AuthModule } from './modules/auth/auth.module' +import { JwtAuthGuard } from './modules/auth/guards/jwt-auth.guard' +import { RbacGuard } from './modules/auth/guards/rbac.guard' +import { HealthModule } from './modules/health/health.module' +import { SocketModule } from './modules/socket/socket.module' +import { SystemModule } from './modules/system/system.module' +import { TasksModule } from './modules/tasks/tasks.module' +import { TodoModule } from './modules/todo/todo.module' +import { ToolsModule } from './modules/tools/tools.module' +import { DatabaseModule } from './shared/database/database.module' @Module({ imports: [ diff --git a/apps/api/src/common/adapters/fastify.adapter.ts b/apps/api/src/common/adapters/fastify.adapter.ts index 8e72f92..42cc1ea 100644 --- a/apps/api/src/common/adapters/fastify.adapter.ts +++ b/apps/api/src/common/adapters/fastify.adapter.ts @@ -1,12 +1,12 @@ -import FastifyCookie from '@fastify/cookie'; -import FastifyMultipart from '@fastify/multipart'; -import { FastifyAdapter } from '@nestjs/platform-fastify'; +import FastifyCookie from '@fastify/cookie' +import FastifyMultipart from '@fastify/multipart' +import { FastifyAdapter } from '@nestjs/platform-fastify' const app: FastifyAdapter = new FastifyAdapter({ trustProxy: true, logger: false, -}); -export { app as fastifyApp }; +}) +export { app as fastifyApp } app.register(FastifyMultipart as any, { limits: { @@ -14,34 +14,34 @@ app.register(FastifyMultipart as any, { fileSize: 1024 * 1024 * 6, // limit size 6M files: 5, // Max number of file fields }, -}); +}) app.register(FastifyCookie as any, { secret: 'cookie-secret', // 这个 secret 不太重要,不存鉴权相关,无关紧要 -}); +}) app.getInstance().addHook('onRequest', (request, reply, done) => { // set undefined origin - const { origin } = request.headers; + const { origin } = request.headers if (!origin) { - request.headers.origin = request.headers.host; + request.headers.origin = request.headers.host } // forbidden php - const { url } = request; + const { url } = request if (url.endsWith('.php')) { reply.raw.statusMessage = - 'Eh. PHP is not support on this machine. Yep, I also think PHP is bestest programming language. But for me it is beyond my reach.'; + 'Eh. PHP is not support on this machine. Yep, I also think PHP is bestest programming language. But for me it is beyond my reach.' - return reply.code(418).send(); + return reply.code(418).send() } // skip favicon request if (url.match(/favicon.ico$/) || url.match(/manifest.json$/)) { - return reply.code(204).send(); + return reply.code(204).send() } - done(); -}); + done() +}) diff --git a/apps/api/src/common/adapters/socket.adapter.ts b/apps/api/src/common/adapters/socket.adapter.ts index ba6bf60..1019e81 100644 --- a/apps/api/src/common/adapters/socket.adapter.ts +++ b/apps/api/src/common/adapters/socket.adapter.ts @@ -1,3 +1,3 @@ -import { IoAdapter } from '@nestjs/platform-socket.io'; +import { IoAdapter } from '@nestjs/platform-socket.io' -export { IoAdapter }; +export { IoAdapter } diff --git a/apps/api/src/common/decorators/api-result.decorator.ts b/apps/api/src/common/decorators/api-result.decorator.ts index 1f1a880..e77b0c2 100644 --- a/apps/api/src/common/decorators/api-result.decorator.ts +++ b/apps/api/src/common/decorators/api-result.decorator.ts @@ -1,9 +1,9 @@ -import { Type, applyDecorators, HttpStatus } from '@nestjs/common'; -import { ApiExtraModels, ApiResponse, getSchemaPath } from '@nestjs/swagger'; +import { Type, applyDecorators, HttpStatus } from '@nestjs/common' +import { ApiExtraModels, ApiResponse, getSchemaPath } from '@nestjs/swagger' -import { ResOp } from '@/common/model/response.model'; +import { ResOp } from '@/common/model/response.model' -const baseTypeNames = ['String', 'Number', 'Boolean']; +const baseTypeNames = ['String', 'Number', 'Boolean'] /** * @description: 生成返回结果装饰器 @@ -13,11 +13,11 @@ export const ApiResult = >({ isPage, status, }: { - type?: TModel | TModel[]; - isPage?: boolean; - status?: HttpStatus; + type?: TModel | TModel[] + isPage?: boolean + status?: HttpStatus }) => { - let prop = null; + let prop = null if (Array.isArray(type)) { if (isPage) { @@ -39,24 +39,24 @@ export const ApiResult = >({ }, }, }, - }; + } } else { prop = { type: 'array', items: { $ref: getSchemaPath(type[0]) }, - }; + } } } else if (type) { if (type && baseTypeNames.includes(type.name)) { - prop = { type: type.name.toLocaleLowerCase() }; + prop = { type: type.name.toLocaleLowerCase() } } else { - prop = { $ref: getSchemaPath(type) }; + prop = { $ref: getSchemaPath(type) } } } else { - prop = { type: 'null', default: null }; + prop = { type: 'null', default: null } } - const model = Array.isArray(type) ? type[0] : type; + const model = Array.isArray(type) ? type[0] : type return applyDecorators( ApiExtraModels(model), @@ -73,5 +73,5 @@ export const ApiResult = >({ ], }, }), - ); -}; + ) +} diff --git a/apps/api/src/common/decorators/api-token.decorator.ts b/apps/api/src/common/decorators/api-token.decorator.ts index ceea78a..f8fcd0d 100644 --- a/apps/api/src/common/decorators/api-token.decorator.ts +++ b/apps/api/src/common/decorators/api-token.decorator.ts @@ -1,8 +1,8 @@ -import { SetMetadata } from '@nestjs/common'; +import { SetMetadata } from '@nestjs/common' -export const API_TOKEN_DECORATOR_KEY = 'decorator:api_token'; +export const API_TOKEN_DECORATOR_KEY = 'decorator:api_token' /** * 需要协议头携带 Authorization 协议头才可以操作 */ -export const ApiToken = () => SetMetadata(API_TOKEN_DECORATOR_KEY, true); +export const ApiToken = () => SetMetadata(API_TOKEN_DECORATOR_KEY, true) diff --git a/apps/api/src/common/decorators/cookie.decorator.ts b/apps/api/src/common/decorators/cookie.decorator.ts index 11be959..2eaa338 100644 --- a/apps/api/src/common/decorators/cookie.decorator.ts +++ b/apps/api/src/common/decorators/cookie.decorator.ts @@ -1,10 +1,10 @@ -import type { ExecutionContext } from '@nestjs/common'; -import { createParamDecorator } from '@nestjs/common'; -import type { FastifyRequest } from 'fastify'; +import type { ExecutionContext } from '@nestjs/common' +import { createParamDecorator } from '@nestjs/common' +import type { FastifyRequest } from 'fastify' export const Cookies = createParamDecorator( (data: string, ctx: ExecutionContext) => { - const request = ctx.switchToHttp().getRequest(); - return data ? request.cookies?.[data] : request.cookies; + const request = ctx.switchToHttp().getRequest() + return data ? request.cookies?.[data] : request.cookies }, -); +) diff --git a/apps/api/src/common/decorators/field.decorator.ts b/apps/api/src/common/decorators/field.decorator.ts index aecb0a9..18f9757 100644 --- a/apps/api/src/common/decorators/field.decorator.ts +++ b/apps/api/src/common/decorators/field.decorator.ts @@ -1,4 +1,4 @@ -import { applyDecorators } from '@nestjs/common'; +import { applyDecorators } from '@nestjs/common' import { IsBoolean, IsDate, @@ -12,8 +12,8 @@ import { MaxLength, Min, MinLength, -} from 'class-validator'; -import { isNumber } from 'lodash'; +} from 'class-validator' +import { isNumber } from 'lodash' import { ToArray, @@ -23,62 +23,62 @@ import { ToNumber, ToTrim, ToUpperCase, -} from './transform.decorator'; +} from './transform.decorator' interface IOptionalOptions { - required?: boolean; + required?: boolean } interface INumberFieldOptions extends IOptionalOptions { - each?: boolean; - int?: boolean; - min?: number; - max?: number; - positive?: boolean; + each?: boolean + int?: boolean + min?: number + max?: number + positive?: boolean } interface IStringFieldOptions extends IOptionalOptions { - each?: boolean; - minLength?: number; - maxLength?: number; - lowerCase?: boolean; - upperCase?: boolean; + each?: boolean + minLength?: number + maxLength?: number + lowerCase?: boolean + upperCase?: boolean } export function NumberField( options: INumberFieldOptions = {}, ): PropertyDecorator { - const { each, min, max, int, positive, required = true } = options; + const { each, min, max, int, positive, required = true } = options - const decorators = [ToNumber()]; + const decorators = [ToNumber()] if (each) { - decorators.push(ToArray()); + decorators.push(ToArray()) } if (int) { - decorators.push(IsInt({ each })); + decorators.push(IsInt({ each })) } else { - decorators.push(IsNumber({}, { each })); + decorators.push(IsNumber({}, { each })) } if (isNumber(min)) { - decorators.push(Min(min, { each })); + decorators.push(Min(min, { each })) } if (isNumber(max)) { - decorators.push(Max(max, { each })); + decorators.push(Max(max, { each })) } if (positive) { - decorators.push(IsPositive({ each })); + decorators.push(IsPositive({ each })) } if (!required) { - decorators.push(IsOptional()); + decorators.push(IsOptional()) } - return applyDecorators(...decorators); + return applyDecorators(...decorators) } export function StringField( @@ -91,61 +91,61 @@ export function StringField( lowerCase, upperCase, required = true, - } = options; + } = options - const decorators = [IsString({ each }), ToTrim()]; + const decorators = [IsString({ each }), ToTrim()] if (each) { - decorators.push(ToArray()); + decorators.push(ToArray()) } if (isNumber(minLength)) { - decorators.push(MinLength(minLength, { each })); + decorators.push(MinLength(minLength, { each })) } if (isNumber(maxLength)) { - decorators.push(MaxLength(maxLength, { each })); + decorators.push(MaxLength(maxLength, { each })) } if (lowerCase) { - decorators.push(ToLowerCase()); + decorators.push(ToLowerCase()) } if (upperCase) { - decorators.push(ToUpperCase()); + decorators.push(ToUpperCase()) } if (!required) { - decorators.push(IsOptional()); + decorators.push(IsOptional()) } else { - decorators.push(IsNotEmpty({ each })); + decorators.push(IsNotEmpty({ each })) } - return applyDecorators(...decorators); + return applyDecorators(...decorators) } export function BooleanField( options: IOptionalOptions = {}, ): PropertyDecorator { - const decorators = [ToBoolean(), IsBoolean()]; + const decorators = [ToBoolean(), IsBoolean()] - const { required = true } = options; + const { required = true } = options if (!required) { - decorators.push(IsOptional()); + decorators.push(IsOptional()) } - return applyDecorators(...decorators); + return applyDecorators(...decorators) } export function DateField(options: IOptionalOptions = {}): PropertyDecorator { - const decorators = [ToDate(), IsDate()]; + const decorators = [ToDate(), IsDate()] - const { required = true } = options; + const { required = true } = options if (!required) { - decorators.push(IsOptional()); + decorators.push(IsOptional()) } - return applyDecorators(...decorators); + return applyDecorators(...decorators) } diff --git a/apps/api/src/common/decorators/http.decorator.ts b/apps/api/src/common/decorators/http.decorator.ts index 34f96ca..b23df2d 100644 --- a/apps/api/src/common/decorators/http.decorator.ts +++ b/apps/api/src/common/decorators/http.decorator.ts @@ -1,13 +1,13 @@ -import type { ExecutionContext } from '@nestjs/common'; +import type { ExecutionContext } from '@nestjs/common' -import { createParamDecorator } from '@nestjs/common'; -import type { FastifyRequest } from 'fastify'; +import { createParamDecorator } from '@nestjs/common' +import type { FastifyRequest } from 'fastify' /** * 快速获取IP */ export const Ip = createParamDecorator((_, context: ExecutionContext) => { - const request = context.switchToHttp().getRequest(); + const request = context.switchToHttp().getRequest() return ( // 判断是否有反向代理 IP @@ -16,13 +16,13 @@ export const Ip = createParamDecorator((_, context: ExecutionContext) => { // 判断后端的 socket 的 IP request.socket.remoteAddress ).replace('::ffff:', '') - ); -}); + ) +}) /** * 快速获取request path,并不包括url params */ export const Uri = createParamDecorator((_, context: ExecutionContext) => { - const request = context.switchToHttp().getRequest(); - return request.routerPath; -}); + const request = context.switchToHttp().getRequest() + return request.routerPath +}) diff --git a/apps/api/src/common/decorators/id-param.decorator.ts b/apps/api/src/common/decorators/id-param.decorator.ts index b9b92bd..d9866fa 100644 --- a/apps/api/src/common/decorators/id-param.decorator.ts +++ b/apps/api/src/common/decorators/id-param.decorator.ts @@ -1,5 +1,5 @@ -import { Param, ParseIntPipe } from '@nestjs/common'; +import { Param, ParseIntPipe } from '@nestjs/common' export const IdParam = () => { - return Param('id', new ParseIntPipe()); -}; + return Param('id', new ParseIntPipe()) +} diff --git a/apps/api/src/common/decorators/skip-transform.decorator.ts b/apps/api/src/common/decorators/skip-transform.decorator.ts index 668011f..6330de0 100644 --- a/apps/api/src/common/decorators/skip-transform.decorator.ts +++ b/apps/api/src/common/decorators/skip-transform.decorator.ts @@ -1,9 +1,9 @@ -import { SetMetadata } from '@nestjs/common'; +import { SetMetadata } from '@nestjs/common' -export const SKIP_TRANSFORM_DECORATOR_KEY = 'decorator:skip_transform'; +export const SKIP_TRANSFORM_DECORATOR_KEY = 'decorator:skip_transform' /** * 当不需要转换成基础返回格式时添加该装饰器 */ export const SkipTransform = () => - SetMetadata(SKIP_TRANSFORM_DECORATOR_KEY, true); + SetMetadata(SKIP_TRANSFORM_DECORATOR_KEY, true) diff --git a/apps/api/src/common/decorators/swagger.decorator.ts b/apps/api/src/common/decorators/swagger.decorator.ts index 92744d8..4809e5c 100644 --- a/apps/api/src/common/decorators/swagger.decorator.ts +++ b/apps/api/src/common/decorators/swagger.decorator.ts @@ -1,11 +1,11 @@ -import { applyDecorators } from '@nestjs/common'; -import { ApiSecurity } from '@nestjs/swagger'; +import { applyDecorators } from '@nestjs/common' +import { ApiSecurity } from '@nestjs/swagger' -export const API_SECURITY_AUTH = 'auth'; +export const API_SECURITY_AUTH = 'auth' /** * like to @ApiSecurity('auth') */ export function ApiSecurityAuth(): ClassDecorator & MethodDecorator { - return applyDecorators(ApiSecurity(API_SECURITY_AUTH)); + return applyDecorators(ApiSecurity(API_SECURITY_AUTH)) } diff --git a/apps/api/src/common/decorators/transform.decorator.ts b/apps/api/src/common/decorators/transform.decorator.ts index ed26bf6..159768d 100644 --- a/apps/api/src/common/decorators/transform.decorator.ts +++ b/apps/api/src/common/decorators/transform.decorator.ts @@ -1,5 +1,5 @@ -import { Transform } from 'class-transformer'; -import { castArray, isArray, isNil, trim } from 'lodash'; +import { Transform } from 'class-transformer' +import { castArray, isArray, isNil, trim } from 'lodash' /** * convert string to number @@ -7,16 +7,16 @@ import { castArray, isArray, isNil, trim } from 'lodash'; export function ToNumber(): PropertyDecorator { return Transform( (params) => { - const value = params.value as string[] | string; + const value = params.value as string[] | string if (isArray(value)) { - return value.map((v) => Number(v)); + return value.map((v) => Number(v)) } - return Number(value); + return Number(value) }, { toClassOnly: true }, - ); + ) } /** @@ -25,16 +25,16 @@ export function ToNumber(): PropertyDecorator { export function ToInt(): PropertyDecorator { return Transform( (params) => { - const value = params.value as string[] | string; + const value = params.value as string[] | string if (isArray(value)) { - return value.map((v) => Number.parseInt(v)); + return value.map((v) => Number.parseInt(v)) } - return Number.parseInt(value); + return Number.parseInt(value) }, { toClassOnly: true }, - ); + ) } /** @@ -45,15 +45,15 @@ export function ToBoolean(): PropertyDecorator { (params) => { switch (params.value) { case 'true': - return true; + return true case 'false': - return false; + return false default: - return params.value; + return params.value } }, { toClassOnly: true }, - ); + ) } /** @@ -62,14 +62,14 @@ export function ToBoolean(): PropertyDecorator { export function ToDate(): PropertyDecorator { return Transform( (params) => { - const { value } = params; + const { value } = params - if (!value) return; + if (!value) return - return new Date(value); + return new Date(value) }, { toClassOnly: true }, - ); + ) } /** @@ -78,14 +78,14 @@ export function ToDate(): PropertyDecorator { export function ToArray(): PropertyDecorator { return Transform( (params) => { - const { value } = params; + const { value } = params - if (isNil(value)) return []; + if (isNil(value)) return [] - return castArray(value); + return castArray(value) }, { toClassOnly: true }, - ); + ) } /** @@ -94,16 +94,16 @@ export function ToArray(): PropertyDecorator { export function ToTrim(): PropertyDecorator { return Transform( (params) => { - const value = params.value as string[] | string; + const value = params.value as string[] | string if (isArray(value)) { - return value.map((v) => trim(v)); + return value.map((v) => trim(v)) } - return trim(value); + return trim(value) }, { toClassOnly: true }, - ); + ) } /** @@ -112,18 +112,18 @@ export function ToTrim(): PropertyDecorator { export function ToLowerCase(): PropertyDecorator { return Transform( (params) => { - const value = params.value as string[] | string; + const value = params.value as string[] | string - if (!value) return; + if (!value) return if (isArray(value)) { - return value.map((v) => v.toLowerCase()); + return value.map((v) => v.toLowerCase()) } - return value.toLowerCase(); + return value.toLowerCase() }, { toClassOnly: true }, - ); + ) } /** @@ -132,16 +132,16 @@ export function ToLowerCase(): PropertyDecorator { export function ToUpperCase(): PropertyDecorator { return Transform( (params) => { - const value = params.value as string[] | string; + const value = params.value as string[] | string - if (!value) return; + if (!value) return if (isArray(value)) { - return value.map((v) => v.toUpperCase()); + return value.map((v) => v.toUpperCase()) } - return value.toUpperCase(); + return value.toUpperCase() }, { toClassOnly: true }, - ); + ) } diff --git a/apps/api/src/common/dto/abstract.dto.ts b/apps/api/src/common/dto/abstract.dto.ts index 8c7ff84..a1ac267 100644 --- a/apps/api/src/common/dto/abstract.dto.ts +++ b/apps/api/src/common/dto/abstract.dto.ts @@ -1,23 +1,23 @@ -import { Type } from 'class-transformer'; -import { IsDate, IsInt, IsOptional, Min } from 'class-validator'; +import { Type } from 'class-transformer' +import { IsDate, IsInt, IsOptional, Min } from 'class-validator' export abstract class AbstractDto { @Type(() => Number) @IsInt() @Min(1) @IsOptional() - id!: number; + id!: number @Type(() => Date) @IsDate() @IsOptional() - createdAt?: Date; + createdAt?: Date @Type(() => Date) @IsDate() @IsOptional() - updatedAt?: Date; + updatedAt?: Date @IsOptional() - deleted?: boolean; + deleted?: boolean } diff --git a/apps/api/src/common/dto/delete.dto.ts b/apps/api/src/common/dto/delete.dto.ts index 6f87b9e..5157aae 100644 --- a/apps/api/src/common/dto/delete.dto.ts +++ b/apps/api/src/common/dto/delete.dto.ts @@ -1,8 +1,8 @@ -import { IsDefined, IsNotEmpty, IsNumber } from 'class-validator'; +import { IsDefined, IsNotEmpty, IsNumber } from 'class-validator' export class BatchDeleteDto { @IsDefined() @IsNotEmpty() @IsNumber({}, { each: true }) - ids: number[]; + ids: number[] } diff --git a/apps/api/src/common/dto/list.dto.ts b/apps/api/src/common/dto/list.dto.ts index 2cd7913..baebb0a 100644 --- a/apps/api/src/common/dto/list.dto.ts +++ b/apps/api/src/common/dto/list.dto.ts @@ -1,7 +1,7 @@ -import { ApiPropertyOptional } from '@nestjs/swagger'; -import { IsOptional } from 'class-validator'; +import { ApiPropertyOptional } from '@nestjs/swagger' +import { IsOptional } from 'class-validator' -import { PageOptionsDto } from './page-options.dto'; +import { PageOptionsDto } from './page-options.dto' export class ListDTO> extends PageOptionsDto { @ApiPropertyOptional({ @@ -12,5 +12,5 @@ export class ListDTO> extends PageOptionsDto { }, }) @IsOptional() - query?: Partial = {}; + query?: Partial = {} } diff --git a/apps/api/src/common/dto/page-options.dto.ts b/apps/api/src/common/dto/page-options.dto.ts index 1986c7c..90b280a 100644 --- a/apps/api/src/common/dto/page-options.dto.ts +++ b/apps/api/src/common/dto/page-options.dto.ts @@ -1,6 +1,6 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { Transform, Type } from 'class-transformer'; -import { IsEnum, IsInt, IsOptional, IsString, Max, Min } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger' +import { Transform, Type } from 'class-transformer' +import { IsEnum, IsInt, IsOptional, IsString, Max, Min } from 'class-validator' // allow custom order export enum Order { @@ -17,7 +17,7 @@ export class PageOptionsDto { @IsInt() @Min(1) @IsOptional({ always: true }) - page?: number = 1; + page?: number = 1 @ApiProperty({ minimum: 1, @@ -29,12 +29,12 @@ export class PageOptionsDto { @Min(1) @Max(100) @IsOptional({ always: true }) - pageSize?: number = 10; + pageSize?: number = 10 @ApiProperty({}) @IsString() @IsOptional() - field?: keyof T; + field?: keyof T @ApiProperty({ enum: Order, @@ -43,11 +43,11 @@ export class PageOptionsDto { @IsEnum(Order) @IsOptional() @Transform(({ value }) => (value === 'ascend' ? Order.ASC : Order.DESC)) - order?: Order = Order.ASC; + order?: Order = Order.ASC @ApiProperty({}) @Type(() => Number) @IsInt() @IsOptional({ always: true }) - _t?: number; + _t?: number } diff --git a/apps/api/src/common/entity/abstract.entity.ts b/apps/api/src/common/entity/abstract.entity.ts index b1b745f..b708b68 100644 --- a/apps/api/src/common/entity/abstract.entity.ts +++ b/apps/api/src/common/entity/abstract.entity.ts @@ -2,15 +2,15 @@ import { PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, -} from 'typeorm'; +} from 'typeorm' export abstract class AbstractEntity { @PrimaryGeneratedColumn() - id: number; + id: number @CreateDateColumn({ name: 'created_at' }) - createdAt: Date; + createdAt: Date @UpdateDateColumn({ name: 'updated_at' }) - updatedAt: Date; + updatedAt: Date } diff --git a/apps/api/src/common/exceptions/biz.exception.ts b/apps/api/src/common/exceptions/biz.exception.ts index 913375b..8f20c10 100644 --- a/apps/api/src/common/exceptions/biz.exception.ts +++ b/apps/api/src/common/exceptions/biz.exception.ts @@ -1,10 +1,10 @@ -import { HttpException, HttpStatus } from '@nestjs/common'; +import { HttpException, HttpStatus } from '@nestjs/common' -import { ErrorEnum } from '@/constants/error-code.constant'; -import { RESPONSE_SUCCESS_CODE } from '@/constants/response.constant'; +import { ErrorEnum } from '@/constants/error-code.constant' +import { RESPONSE_SUCCESS_CODE } from '@/constants/response.constant' export class BusinessException extends HttpException { - private errorCode: number; + private errorCode: number constructor(error: ErrorEnum | string) { // 如果是非 ErrorEnum @@ -15,26 +15,26 @@ export class BusinessException extends HttpException { message: error, }), HttpStatus.OK, - ); - this.errorCode = RESPONSE_SUCCESS_CODE; - return; + ) + this.errorCode = RESPONSE_SUCCESS_CODE + return } - const [code, message] = error.split(':'); + const [code, message] = error.split(':') super( HttpException.createBody({ code, message, }), HttpStatus.OK, - ); + ) - this.errorCode = Number(code); + this.errorCode = Number(code) } getErrorCode(): number { - return this.errorCode; + return this.errorCode } } -export { BusinessException as BizException }; +export { BusinessException as BizException } diff --git a/apps/api/src/common/exceptions/not-found.exception.ts b/apps/api/src/common/exceptions/not-found.exception.ts index 95b034d..c6cb536 100644 --- a/apps/api/src/common/exceptions/not-found.exception.ts +++ b/apps/api/src/common/exceptions/not-found.exception.ts @@ -1,10 +1,10 @@ -import { NotFoundException } from '@nestjs/common'; -import { sample } from 'lodash'; +import { NotFoundException } from '@nestjs/common' +import { sample } from 'lodash' -export const NotFoundMessage = ['404, Not Found']; +export const NotFoundMessage = ['404, Not Found'] export class CannotFindException extends NotFoundException { constructor() { - super(sample(NotFoundMessage)); + super(sample(NotFoundMessage)) } } diff --git a/apps/api/src/common/exceptions/socket.exception.ts b/apps/api/src/common/exceptions/socket.exception.ts index 8d6219f..2de1f3f 100644 --- a/apps/api/src/common/exceptions/socket.exception.ts +++ b/apps/api/src/common/exceptions/socket.exception.ts @@ -1,38 +1,38 @@ -import { HttpException } from '@nestjs/common'; -import { WsException } from '@nestjs/websockets'; +import { HttpException } from '@nestjs/common' +import { WsException } from '@nestjs/websockets' -import { ErrorEnum } from '@/constants/error-code.constant'; +import { ErrorEnum } from '@/constants/error-code.constant' export class SocketException extends WsException { - private errorCode: number; + private errorCode: number - constructor(message: string); - constructor(error: ErrorEnum); + constructor(message: string) + constructor(error: ErrorEnum) constructor(...args: any) { - const error = args[0]; + const error = args[0] if (typeof error === 'string') { super( HttpException.createBody({ code: 0, message: error, }), - ); - this.errorCode = 0; - return; + ) + this.errorCode = 0 + return } - const [code, message] = error.split(':'); + const [code, message] = error.split(':') super( HttpException.createBody({ code, message, }), - ); + ) - this.errorCode = Number(code); + this.errorCode = Number(code) } getErrorCode(): number { - return this.errorCode; + return this.errorCode } } diff --git a/apps/api/src/common/filters/any-exception.filter.ts b/apps/api/src/common/filters/any-exception.filter.ts index 5409a7e..5b27c75 100644 --- a/apps/api/src/common/filters/any-exception.filter.ts +++ b/apps/api/src/common/filters/any-exception.filter.ts @@ -5,87 +5,85 @@ import { HttpException, HttpStatus, Logger, -} from '@nestjs/common'; -import { FastifyReply, FastifyRequest } from 'fastify'; +} from '@nestjs/common' +import { FastifyReply, FastifyRequest } from 'fastify' -import { BusinessException } from '@/common/exceptions/biz.exception'; -import { ErrorEnum } from '@/constants/error-code.constant'; +import { BusinessException } from '@/common/exceptions/biz.exception' +import { ErrorEnum } from '@/constants/error-code.constant' -import { isDev } from '@/global/env'; +import { isDev } from '@/global/env' type myError = { - readonly status: number; - readonly statusCode?: number; + readonly status: number + readonly statusCode?: number - readonly message?: string; -}; + readonly message?: string +} @Catch() export class AllExceptionsFilter implements ExceptionFilter { - private readonly logger = new Logger(AllExceptionsFilter.name); + private readonly logger = new Logger(AllExceptionsFilter.name) constructor() { - this.registerCatchAllExceptionsHook(); + this.registerCatchAllExceptionsHook() } catch(exception: unknown, host: ArgumentsHost) { - const ctx = host.switchToHttp(); - const request = ctx.getRequest(); - const response = ctx.getResponse(); + const ctx = host.switchToHttp() + const request = ctx.getRequest() + const response = ctx.getResponse() - const url = request.raw.url!; + const url = request.raw.url! const status = exception instanceof HttpException ? exception.getStatus() : (exception as myError)?.status || (exception as myError)?.statusCode || - HttpStatus.INTERNAL_SERVER_ERROR; + HttpStatus.INTERNAL_SERVER_ERROR let message = (exception as any)?.response?.message || (exception as myError)?.message || - `${exception}`; + `${exception}` // 系统内部错误时 if ( status === HttpStatus.INTERNAL_SERVER_ERROR && !(exception instanceof BusinessException) ) { - Logger.error(exception, undefined, 'Catch'); + Logger.error(exception, undefined, 'Catch') // 生产环境下隐藏错误信息 if (!isDev) { - message = ErrorEnum.SERVER_ERROR?.split(':')[1]; + message = ErrorEnum.SERVER_ERROR?.split(':')[1] } } else { this.logger.warn( `错误信息:(${status}) ${message} Path: ${decodeURI(url)}`, - ); + ) } const apiErrorCode: number = - exception instanceof BusinessException - ? exception.getErrorCode() - : status; + exception instanceof BusinessException ? exception.getErrorCode() : status // 返回基础响应结果 const resBody: IBaseResponse = { code: apiErrorCode, message, data: null, - }; + } - response.status(status).send(resBody); + response.status(status).send(resBody) } registerCatchAllExceptionsHook() { process.on('unhandledRejection', (reason) => { - console.error('unhandledRejection: ', reason); - }); + console.error('unhandledRejection: ', reason) + }) process.on('uncaughtException', (err) => { - console.error('uncaughtException: ', err); - }); + console.error('uncaughtException: ', err) + }) } } diff --git a/apps/api/src/common/interceptors/logging.interceptor.ts b/apps/api/src/common/interceptors/logging.interceptor.ts index 8f5d1f7..79bd488 100644 --- a/apps/api/src/common/interceptors/logging.interceptor.ts +++ b/apps/api/src/common/interceptors/logging.interceptor.ts @@ -4,27 +4,27 @@ import { Injectable, Logger, NestInterceptor, -} from '@nestjs/common'; -import { Observable, tap } from 'rxjs'; +} from '@nestjs/common' +import { Observable, tap } from 'rxjs' @Injectable() export class LoggingInterceptor implements NestInterceptor { - private logger = new Logger(LoggingInterceptor.name, { timestamp: false }); + private logger = new Logger(LoggingInterceptor.name, { timestamp: false }) intercept( context: ExecutionContext, next: CallHandler, ): Observable { - const call$ = next.handle(); - const request = context.switchToHttp().getRequest(); - const content = `${request.method} -> ${request.url}`; - this.logger.debug(`+++ 请求:${content}`); - const now = Date.now(); + const call$ = next.handle() + const request = context.switchToHttp().getRequest() + const content = `${request.method} -> ${request.url}` + this.logger.debug(`+++ 请求:${content}`) + const now = Date.now() return call$.pipe( tap(() => this.logger.debug(`--- 响应:${content}${` +${Date.now() - now}ms`}`), ), - ); + ) } } diff --git a/apps/api/src/common/interceptors/timeout.interceptor.ts b/apps/api/src/common/interceptors/timeout.interceptor.ts index 5387869..74aa07c 100644 --- a/apps/api/src/common/interceptors/timeout.interceptor.ts +++ b/apps/api/src/common/interceptors/timeout.interceptor.ts @@ -4,9 +4,9 @@ import { ExecutionContext, CallHandler, RequestTimeoutException, -} from '@nestjs/common'; -import { Observable, throwError, TimeoutError } from 'rxjs'; -import { catchError, timeout } from 'rxjs/operators'; +} from '@nestjs/common' +import { Observable, throwError, TimeoutError } from 'rxjs' +import { catchError, timeout } from 'rxjs/operators' @Injectable() export class TimeoutInterceptor implements NestInterceptor { @@ -17,10 +17,10 @@ export class TimeoutInterceptor implements NestInterceptor { timeout(this.time), catchError((err) => { if (err instanceof TimeoutError) { - return throwError(new RequestTimeoutException('请求超时')); + return throwError(new RequestTimeoutException('请求超时')) } - return throwError(err); + return throwError(err) }), - ); + ) } } diff --git a/apps/api/src/common/interceptors/transform.interceptor.ts b/apps/api/src/common/interceptors/transform.interceptor.ts index 2492c5b..36a94f3 100644 --- a/apps/api/src/common/interceptors/transform.interceptor.ts +++ b/apps/api/src/common/interceptors/transform.interceptor.ts @@ -3,14 +3,14 @@ import { ExecutionContext, HttpStatus, NestInterceptor, -} from '@nestjs/common'; -import { Reflector } from '@nestjs/core'; -import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; +} from '@nestjs/common' +import { Reflector } from '@nestjs/core' +import { Observable } from 'rxjs' +import { map } from 'rxjs/operators' -import { ResOp } from '@/common/model/response.model'; +import { ResOp } from '@/common/model/response.model' -import { SKIP_TRANSFORM_DECORATOR_KEY } from '../decorators/skip-transform.decorator'; +import { SKIP_TRANSFORM_DECORATOR_KEY } from '../decorators/skip-transform.decorator' /** * 统一处理返回接口结果,如果不需要则添加 @SkipTransform 装饰器 @@ -25,8 +25,8 @@ export class TransformInterceptor implements NestInterceptor { const isSkipTransform = this.reflector.get( SKIP_TRANSFORM_DECORATOR_KEY, context.getHandler(), - ); - if (isSkipTransform) return next.handle(); + ) + if (isSkipTransform) return next.handle() return next.handle().pipe( map((data) => { @@ -35,8 +35,8 @@ export class TransformInterceptor implements NestInterceptor { // return data; // } - return new ResOp(HttpStatus.OK, data ?? null); + return new ResOp(HttpStatus.OK, data ?? null) }), - ); + ) } } diff --git a/apps/api/src/common/model/response.model.ts b/apps/api/src/common/model/response.model.ts index b2eb2b4..3d71a0a 100644 --- a/apps/api/src/common/model/response.model.ts +++ b/apps/api/src/common/model/response.model.ts @@ -1,42 +1,42 @@ -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty } from '@nestjs/swagger' import { RESPONSE_SUCCESS_CODE, RESPONSE_SUCCESS_MSG, -} from '@/constants/response.constant'; +} from '@/constants/response.constant' export class ResOp { @ApiProperty({ type: 'object' }) - data?: T; + data?: T @ApiProperty({ type: 'number', default: RESPONSE_SUCCESS_CODE }) - code: number; + code: number @ApiProperty({ type: 'string', default: RESPONSE_SUCCESS_MSG }) - message: string; + message: string constructor(code: number, data: T, message = RESPONSE_SUCCESS_MSG) { - this.code = code; - this.data = data; - this.message = message; + this.code = code + this.data = data + this.message = message } static success(data?: T, message?: string) { - return new ResOp(RESPONSE_SUCCESS_CODE, data, message); + return new ResOp(RESPONSE_SUCCESS_CODE, data, message) } static error(code: number, message) { - return new ResOp(code, {}, message); + return new ResOp(code, {}, message) } } export class TreeResult { @ApiProperty() - id: number; + id: number @ApiProperty() - parentId: number; + parentId: number @ApiProperty() - children?: TreeResult[]; + children?: TreeResult[] } diff --git a/apps/api/src/common/pipes/parse-int.pipe.ts b/apps/api/src/common/pipes/parse-int.pipe.ts index 31288d8..97e099e 100644 --- a/apps/api/src/common/pipes/parse-int.pipe.ts +++ b/apps/api/src/common/pipes/parse-int.pipe.ts @@ -3,16 +3,16 @@ import { BadRequestException, Injectable, PipeTransform, -} from '@nestjs/common'; +} from '@nestjs/common' @Injectable() export class ParseIntPipe implements PipeTransform { transform(value: string, metadata: ArgumentMetadata): number { - const val = parseInt(value, 10); + const val = parseInt(value, 10) // eslint-disable-next-line no-restricted-globals if (isNaN(val)) { - throw new BadRequestException('id validation failed'); + throw new BadRequestException('id validation failed') } - return val; + return val } } diff --git a/apps/api/src/config/app.config.ts b/apps/api/src/config/app.config.ts index d669411..8a8c912 100644 --- a/apps/api/src/config/app.config.ts +++ b/apps/api/src/config/app.config.ts @@ -1,6 +1,6 @@ -import { ConfigType, registerAs } from '@nestjs/config'; +import { ConfigType, registerAs } from '@nestjs/config' -import { env, envNumber } from '@/global/env'; +import { env, envNumber } from '@/global/env' export const AppConfig = registerAs('app', () => ({ name: env('APP_NAME'), @@ -15,6 +15,6 @@ export const AppConfig = registerAs('app', () => ({ level: env('LOGGER_LEVEL'), maxFiles: envNumber('LOGGER_MAX_FILES'), }, -})); +})) -export type IAppConfig = ConfigType; +export type IAppConfig = ConfigType diff --git a/apps/api/src/config/database.config.ts b/apps/api/src/config/database.config.ts index 9239b03..a31ba49 100644 --- a/apps/api/src/config/database.config.ts +++ b/apps/api/src/config/database.config.ts @@ -1,13 +1,13 @@ -import { ConfigType, registerAs } from '@nestjs/config'; +import { ConfigType, registerAs } from '@nestjs/config' -import { DataSource, DataSourceOptions } from 'typeorm'; +import { DataSource, DataSourceOptions } from 'typeorm' -import { env, envBoolean, envNumber } from '@/global/env'; +import { env, envBoolean, envNumber } from '@/global/env' // eslint-disable-next-line import/order -import dotenv from 'dotenv'; +import dotenv from 'dotenv' -dotenv.config({ path: `.env.${process.env.NODE_ENV}` }); +dotenv.config({ path: `.env.${process.env.NODE_ENV}` }) const dataSourceOptions: DataSourceOptions = { type: 'mysql', @@ -20,15 +20,15 @@ const dataSourceOptions: DataSourceOptions = { entities: ['dist/modules/**/*.entity{.ts,.js}'], migrations: ['dist/migrations/*{.ts,.js}'], subscribers: ['dist/modules/**/*.subscriber{.ts,.js}'], -}; +} export const DatabaseConfig = registerAs( 'database', (): DataSourceOptions => dataSourceOptions, -); +) -export type IDatabaseConfig = ConfigType; +export type IDatabaseConfig = ConfigType -const dataSource = new DataSource(dataSourceOptions); +const dataSource = new DataSource(dataSourceOptions) -export default dataSource; +export default dataSource diff --git a/apps/api/src/config/index.ts b/apps/api/src/config/index.ts index 2090038..38e630f 100644 --- a/apps/api/src/config/index.ts +++ b/apps/api/src/config/index.ts @@ -1,7 +1,7 @@ -export * from './app.config'; -export * from './redis.config'; -export * from './database.config'; -export * from './swagger.config'; -export * from './security.config'; -export * from './mailer.config'; -export * from './sms.config'; +export * from './app.config' +export * from './redis.config' +export * from './database.config' +export * from './swagger.config' +export * from './security.config' +export * from './mailer.config' +export * from './sms.config' diff --git a/apps/api/src/config/mailer.config.ts b/apps/api/src/config/mailer.config.ts index 9255a60..40fc007 100644 --- a/apps/api/src/config/mailer.config.ts +++ b/apps/api/src/config/mailer.config.ts @@ -1,6 +1,6 @@ -import { ConfigType, registerAs } from '@nestjs/config'; +import { ConfigType, registerAs } from '@nestjs/config' -import { env, envNumber } from '@/global/env'; +import { env, envNumber } from '@/global/env' export const MailerConfig = registerAs('mailer', () => ({ host: env('SMTP_HOST'), @@ -11,6 +11,6 @@ export const MailerConfig = registerAs('mailer', () => ({ user: env('SMTP_USER'), pass: env('SMTP_PASS'), }, -})); +})) -export type IMailerConfig = ConfigType; +export type IMailerConfig = ConfigType diff --git a/apps/api/src/config/redis.config.ts b/apps/api/src/config/redis.config.ts index f5b2eb8..357f0c9 100644 --- a/apps/api/src/config/redis.config.ts +++ b/apps/api/src/config/redis.config.ts @@ -1,12 +1,12 @@ -import { ConfigType, registerAs } from '@nestjs/config'; +import { ConfigType, registerAs } from '@nestjs/config' -import { env, envNumber } from '@/global/env'; +import { env, envNumber } from '@/global/env' export const RedisConfig = registerAs('redis', () => ({ host: env('REDIS_HOST', '127.0.0.1'), port: envNumber('REDIS_PORT', 6379), password: env('REDIS_PASSWORD'), db: envNumber('REDIS_DB'), -})); +})) -export type IRedisConfig = ConfigType; +export type IRedisConfig = ConfigType diff --git a/apps/api/src/config/security.config.ts b/apps/api/src/config/security.config.ts index 3a7df7d..594757f 100644 --- a/apps/api/src/config/security.config.ts +++ b/apps/api/src/config/security.config.ts @@ -1,12 +1,12 @@ -import { ConfigType, registerAs } from '@nestjs/config'; +import { ConfigType, registerAs } from '@nestjs/config' -import { env, envNumber } from '@/global/env'; +import { env, envNumber } from '@/global/env' export const SecurityConfig = registerAs('security', () => ({ jwtSecret: env('JWT_SECRET'), jwtExprire: envNumber('JWT_EXPIRE'), refreshSecret: env('REFRESH_TOKEN_SECRET'), refreshExpire: envNumber('REFRESH_TOKEN_EXPIRE'), -})); +})) -export type ISecurityConfig = ConfigType; +export type ISecurityConfig = ConfigType diff --git a/apps/api/src/config/sms.config.ts b/apps/api/src/config/sms.config.ts index 2037a19..bfcb044 100644 --- a/apps/api/src/config/sms.config.ts +++ b/apps/api/src/config/sms.config.ts @@ -1,6 +1,6 @@ -import { ConfigType, registerAs } from '@nestjs/config'; +import { ConfigType, registerAs } from '@nestjs/config' -import { env } from '@/global/env'; +import { env } from '@/global/env' export const SmsConfig = registerAs('sms', () => ({ sign: env('SMS_SING', 'Youni'), @@ -8,6 +8,6 @@ export const SmsConfig = registerAs('sms', () => ({ appid: env('SMS_APPID', '1400437232'), secretId: env('SMS_SECRET_ID', 'your-secret-id'), secretKey: env('SMS_SECRET_KEY', 'your-secret-key'), -})); +})) -export type ISmsConfig = ConfigType; +export type ISmsConfig = ConfigType diff --git a/apps/api/src/config/swagger.config.ts b/apps/api/src/config/swagger.config.ts index 7cdd705..b0b11c0 100644 --- a/apps/api/src/config/swagger.config.ts +++ b/apps/api/src/config/swagger.config.ts @@ -1,10 +1,10 @@ -import { ConfigType, registerAs } from '@nestjs/config'; +import { ConfigType, registerAs } from '@nestjs/config' -import { env, envBoolean } from '@/global/env'; +import { env, envBoolean } from '@/global/env' export const SwaggerConfig = registerAs('swagger', () => ({ enable: envBoolean('SWAGGER_ENABLE'), path: env('SWAGGER_PATH'), -})); +})) -export type ISwaggerConfig = ConfigType; +export type ISwaggerConfig = ConfigType diff --git a/apps/api/src/constants/response.constant.ts b/apps/api/src/constants/response.constant.ts index 5b5395b..229f70f 100644 --- a/apps/api/src/constants/response.constant.ts +++ b/apps/api/src/constants/response.constant.ts @@ -1,6 +1,6 @@ -export const RESPONSE_SUCCESS_CODE = 200; +export const RESPONSE_SUCCESS_CODE = 200 -export const RESPONSE_SUCCESS_MSG = 'success'; +export const RESPONSE_SUCCESS_MSG = 'success' /** * @description: contentType diff --git a/apps/api/src/constants/system.constant.ts b/apps/api/src/constants/system.constant.ts index 923d26a..b0f3fec 100644 --- a/apps/api/src/constants/system.constant.ts +++ b/apps/api/src/constants/system.constant.ts @@ -1,2 +1,2 @@ -export const SYS_USER_INITPASSWORD = 'sys_user_initPassword'; -export const SYS_API_TOKEN = 'sys_api_token'; +export const SYS_USER_INITPASSWORD = 'sys_user_initPassword' +export const SYS_API_TOKEN = 'sys_api_token' diff --git a/apps/api/src/global/env.ts b/apps/api/src/global/env.ts index 66d6f3d..80ebfd2 100644 --- a/apps/api/src/global/env.ts +++ b/apps/api/src/global/env.ts @@ -1,19 +1,18 @@ -import cluster from 'cluster'; +import cluster from 'cluster' export const isMainCluster = - process.env.NODE_APP_INSTANCE && - parseInt(process.env.NODE_APP_INSTANCE) === 0; -export const isMainProcess = cluster.isPrimary || isMainCluster; + process.env.NODE_APP_INSTANCE && parseInt(process.env.NODE_APP_INSTANCE) === 0 +export const isMainProcess = cluster.isPrimary || isMainCluster -export const isDev = process.env.NODE_ENV === 'development'; +export const isDev = process.env.NODE_ENV === 'development' -export const isTest = !!process.env.TEST; -export const cwd = process.cwd(); +export const isTest = !!process.env.TEST +export const cwd = process.cwd() /** * 基础类型接口 */ -export type BaseType = boolean | number | string | undefined | null; +export type BaseType = boolean | number | string | undefined | null /** * 格式化环境变量 @@ -26,36 +25,36 @@ const fromatValue = ( defaultValue: T, callback?: (value: string) => T, ): T => { - const value: string | undefined = process.env[key]; + const value: string | undefined = process.env[key] if (typeof value === 'undefined') { - return defaultValue; + return defaultValue } if (!callback) { - return value as unknown as T; + return value as unknown as T } - return callback(value); -}; + return callback(value) +} export const env = (key: string, defaultValue: string = '') => - fromatValue(key, defaultValue); + fromatValue(key, defaultValue) export const envString = (key: string, defaultValue: string = '') => - fromatValue(key, defaultValue); + fromatValue(key, defaultValue) export const envNumber = (key: string, defaultValue: number = 0) => fromatValue(key, defaultValue, (value) => { try { - return Number(value); + return Number(value) } catch { - throw new Error(`${key} environment variable is not a number`); + throw new Error(`${key} environment variable is not a number`) } - }); + }) export const envBoolean = (key: string, defaultValue: boolean = false) => fromatValue(key, defaultValue, (value) => { try { - return Boolean(JSON.parse(value)); + return Boolean(JSON.parse(value)) } catch { - throw new Error(`${key} environment variable is not a boolean`); + throw new Error(`${key} environment variable is not a boolean`) } - }); + }) diff --git a/apps/api/src/helper/catchError.ts b/apps/api/src/helper/catchError.ts index e279c12..0da492a 100644 --- a/apps/api/src/helper/catchError.ts +++ b/apps/api/src/helper/catchError.ts @@ -1,5 +1,5 @@ export const catchError = () => { process.on('unhandledRejection', (reason, p) => { - console.log('Promise: ', p, 'Reason: ', reason); - }); -}; + console.log('Promise: ', p, 'Reason: ', reason) + }) +} diff --git a/apps/api/src/helper/paginate/create-pagination.ts b/apps/api/src/helper/paginate/create-pagination.ts index be54d74..a9fb643 100644 --- a/apps/api/src/helper/paginate/create-pagination.ts +++ b/apps/api/src/helper/paginate/create-pagination.ts @@ -1,5 +1,5 @@ -import { IPaginationMeta } from './interface'; -import { Pagination } from './pagination'; +import { IPaginationMeta } from './interface' +import { Pagination } from './pagination' export function createPaginationObject({ items, @@ -7,13 +7,13 @@ export function createPaginationObject({ currentPage, limit, }: { - items: T[]; - totalItems?: number; - currentPage: number; - limit: number; + items: T[] + totalItems?: number + currentPage: number + limit: number }): Pagination { const totalPages = - totalItems !== undefined ? Math.ceil(totalItems / limit) : undefined; + totalItems !== undefined ? Math.ceil(totalItems / limit) : undefined const meta: IPaginationMeta = { totalItems, @@ -21,7 +21,7 @@ export function createPaginationObject({ itemsPerPage: limit, totalPages, currentPage, - }; + } - return new Pagination(items, meta); + return new Pagination(items, meta) } diff --git a/apps/api/src/helper/paginate/index.ts b/apps/api/src/helper/paginate/index.ts index b08ba07..66e9e13 100644 --- a/apps/api/src/helper/paginate/index.ts +++ b/apps/api/src/helper/paginate/index.ts @@ -4,25 +4,25 @@ import { ObjectLiteral, Repository, SelectQueryBuilder, -} from 'typeorm'; +} from 'typeorm' -import { createPaginationObject } from './create-pagination'; -import { IPaginationOptions, PaginationTypeEnum } from './interface'; -import { Pagination } from './pagination'; +import { createPaginationObject } from './create-pagination' +import { IPaginationOptions, PaginationTypeEnum } from './interface' +import { Pagination } from './pagination' -const DEFAULT_LIMIT = 10; -const DEFAULT_PAGE = 1; +const DEFAULT_LIMIT = 10 +const DEFAULT_PAGE = 1 function resolveOptions( options: IPaginationOptions, ): [number, number, PaginationTypeEnum] { - const { page, pageSize, paginationType } = options; + const { page, pageSize, paginationType } = options return [ page || DEFAULT_PAGE, pageSize || DEFAULT_LIMIT, paginationType || PaginationTypeEnum.TAKE_AND_SKIP, - ]; + ] } async function paginateRepository( @@ -30,7 +30,7 @@ async function paginateRepository( options: IPaginationOptions, searchOptions?: FindOptionsWhere | FindManyOptions, ): Promise> { - const [page, limit] = resolveOptions(options); + const [page, limit] = resolveOptions(options) const promises: [Promise, Promise | undefined] = [ repository.find({ @@ -39,45 +39,45 @@ async function paginateRepository( ...searchOptions, }), undefined, - ]; + ] - const [items, total] = await Promise.all(promises); + const [items, total] = await Promise.all(promises) return createPaginationObject({ items, totalItems: total, currentPage: page, limit, - }); + }) } async function paginateQueryBuilder( queryBuilder: SelectQueryBuilder, options: IPaginationOptions, ): Promise> { - const [page, limit, paginationType] = resolveOptions(options); + const [page, limit, paginationType] = resolveOptions(options) if (paginationType === PaginationTypeEnum.TAKE_AND_SKIP) { - queryBuilder.take(limit).skip((page - 1) * limit); + queryBuilder.take(limit).skip((page - 1) * limit) } else { - queryBuilder.limit(limit).offset((page - 1) * limit); + queryBuilder.limit(limit).offset((page - 1) * limit) } - const [items, total] = await queryBuilder.getManyAndCount(); + const [items, total] = await queryBuilder.getManyAndCount() return createPaginationObject({ items, totalItems: total, currentPage: page, limit, - }); + }) } export async function paginateRaw( queryBuilder: SelectQueryBuilder, options: IPaginationOptions, ): Promise> { - const [page, limit, paginationType] = resolveOptions(options); + const [page, limit, paginationType] = resolveOptions(options) const promises: [Promise, Promise | undefined] = [ (paginationType === PaginationTypeEnum.LIMIT_AND_OFFSET @@ -85,23 +85,23 @@ export async function paginateRaw( : queryBuilder.take(limit).skip((page - 1) * limit) ).getRawMany(), queryBuilder.getCount(), - ]; + ] - const [items, total] = await Promise.all(promises); + const [items, total] = await Promise.all(promises) return createPaginationObject({ items, totalItems: total, currentPage: page, limit, - }); + }) } export async function paginateRawAndEntities( queryBuilder: SelectQueryBuilder, options: IPaginationOptions, ): Promise<[Pagination, Partial[]]> { - const [page, limit, paginationType] = resolveOptions(options); + const [page, limit, paginationType] = resolveOptions(options) const promises: [ Promise<{ entities: T[]; raw: T[] }>, @@ -112,9 +112,9 @@ export async function paginateRawAndEntities( : queryBuilder.take(limit).skip((page - 1) * limit) ).getRawAndEntities(), queryBuilder.getCount(), - ]; + ] - const [itemObject, total] = await Promise.all(promises); + const [itemObject, total] = await Promise.all(promises) return [ createPaginationObject({ @@ -124,18 +124,18 @@ export async function paginateRawAndEntities( limit, }), itemObject.raw, - ]; + ] } export async function paginate( repository: Repository, options: IPaginationOptions, searchOptions?: FindOptionsWhere | FindManyOptions, -): Promise>; +): Promise> export async function paginate( queryBuilder: SelectQueryBuilder, options: IPaginationOptions, -): Promise>; +): Promise> export async function paginate( repositoryOrQueryBuilder: Repository | SelectQueryBuilder, @@ -144,5 +144,5 @@ export async function paginate( ) { return repositoryOrQueryBuilder instanceof Repository ? paginateRepository(repositoryOrQueryBuilder, options, searchOptions) - : paginateQueryBuilder(repositoryOrQueryBuilder, options); + : paginateQueryBuilder(repositoryOrQueryBuilder, options) } diff --git a/apps/api/src/helper/paginate/interface.ts b/apps/api/src/helper/paginate/interface.ts index d2cd7d9..e7c6ee2 100644 --- a/apps/api/src/helper/paginate/interface.ts +++ b/apps/api/src/helper/paginate/interface.ts @@ -1,4 +1,4 @@ -import { ObjectLiteral } from 'typeorm'; +import { ObjectLiteral } from 'typeorm' export enum PaginationTypeEnum { LIMIT_AND_OFFSET = 'limit', @@ -6,22 +6,22 @@ export enum PaginationTypeEnum { } export interface IPaginationOptions { - page: number; - pageSize: number; - paginationType?: PaginationTypeEnum; + page: number + pageSize: number + paginationType?: PaginationTypeEnum } export interface IPaginationMeta extends ObjectLiteral { - itemCount: number; - totalItems?: number; - itemsPerPage: number; - totalPages?: number; - currentPage: number; + itemCount: number + totalItems?: number + itemsPerPage: number + totalPages?: number + currentPage: number } export interface IPaginationLinks { - first?: string; - previous?: string; - next?: string; - last?: string; + first?: string + previous?: string + next?: string + last?: string } diff --git a/apps/api/src/helper/paginate/pagination.ts b/apps/api/src/helper/paginate/pagination.ts index d4b2781..5bcf90f 100644 --- a/apps/api/src/helper/paginate/pagination.ts +++ b/apps/api/src/helper/paginate/pagination.ts @@ -1,6 +1,6 @@ -import { ObjectLiteral } from 'typeorm'; +import { ObjectLiteral } from 'typeorm' -import { IPaginationMeta } from './interface'; +import { IPaginationMeta } from './interface' export class Pagination< PaginationObject, diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index edd21a6..5b054ce 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -1,5 +1,5 @@ -import cluster from 'cluster'; -import path from 'path'; +import cluster from 'cluster' +import path from 'path' import { ClassSerializerInterceptor, @@ -7,26 +7,26 @@ import { Logger, UnprocessableEntityException, ValidationPipe, -} from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import { NestFactory, Reflector } from '@nestjs/core'; -import { NestFastifyApplication } from '@nestjs/platform-fastify'; +} from '@nestjs/common' +import { ConfigService } from '@nestjs/config' +import { NestFactory, Reflector } from '@nestjs/core' +import { NestFastifyApplication } from '@nestjs/platform-fastify' import { useContainer } from 'class-validator'; -import { AppModule } from './app.module'; +import { AppModule } from './app.module' -import { fastifyApp } from './common/adapters/fastify.adapter'; -import { IoAdapter } from './common/adapters/socket.adapter'; -import { LoggingInterceptor } from './common/interceptors/logging.interceptor'; -import { TimeoutInterceptor } from './common/interceptors/timeout.interceptor'; -import { TransformInterceptor } from './common/interceptors/transform.interceptor'; -import { IAppConfig } from './config'; -import { isDev, isMainProcess } from './global/env'; -import { setupSwagger } from './setup-swagger'; -import { MyLogger } from './shared/logger/logger.service'; +import { fastifyApp } from './common/adapters/fastify.adapter' +import { IoAdapter } from './common/adapters/socket.adapter' +import { LoggingInterceptor } from './common/interceptors/logging.interceptor' +import { TimeoutInterceptor } from './common/interceptors/timeout.interceptor' +import { TransformInterceptor } from './common/interceptors/transform.interceptor' +import { IAppConfig } from './config' +import { isDev, isMainProcess } from './global/env' +import { setupSwagger } from './setup-swagger' +import { MyLogger } from './shared/logger/logger.service' -declare const module: any; +declare const module: any async function bootstrap() { const app = await NestFactory.create( @@ -36,27 +36,27 @@ async function bootstrap() { bufferLogs: true, snapshot: true, }, - ); + ) - const configService = app.get(ConfigService); + const configService = app.get(ConfigService) - const reflector = app.get(Reflector); + const reflector = app.get(Reflector) // class-validator 的 DTO 类中注入 nest 容器的依赖 - useContainer(app.select(AppModule), { fallbackOnErrors: true }); + useContainer(app.select(AppModule), { fallbackOnErrors: true }) - app.enableCors({ origin: '*', credentials: true }); - app.setGlobalPrefix('api'); - app.useStaticAssets({ root: path.join(__dirname, '..', 'public') }); + app.enableCors({ origin: '*', credentials: true }) + app.setGlobalPrefix('api') + app.useStaticAssets({ root: path.join(__dirname, '..', 'public') }) app.useGlobalInterceptors( new ClassSerializerInterceptor(reflector), new TransformInterceptor(reflector), new TimeoutInterceptor(), - ); + ) if (isDev) { - app.useGlobalInterceptors(new LoggingInterceptor()); + app.useGlobalInterceptors(new LoggingInterceptor()) } app.useGlobalPipes( @@ -70,44 +70,44 @@ async function bootstrap() { exceptionFactory: (errors) => new UnprocessableEntityException( errors.map((e) => { - const rule = Object.keys(e.constraints!)[0]; - const msg = e.constraints![rule]; - return msg; + const rule = Object.keys(e.constraints!)[0] + const msg = e.constraints![rule] + return msg })[0], ), }), - ); + ) // websocket - app.useWebSocketAdapter(new IoAdapter()); + app.useWebSocketAdapter(new IoAdapter()) - const { port } = configService.get('app')!; + const { port } = configService.get('app')! - setupSwagger(app, configService); + setupSwagger(app, configService) await app.listen(port, '0.0.0.0', async () => { - app.useLogger(app.get(MyLogger)); - const url = await app.getUrl(); - const { pid } = process; - const env = cluster.isPrimary; - const prefix = env ? 'P' : 'W'; + app.useLogger(app.get(MyLogger)) + const url = await app.getUrl() + const { pid } = process + const env = cluster.isPrimary + const prefix = env ? 'P' : 'W' if (!isMainProcess) { - return; + return } - const logger = new Logger('NestApplication'); - logger.log(`[${prefix + pid}] Server running on ${url}`); + const logger = new Logger('NestApplication') + logger.log(`[${prefix + pid}] Server running on ${url}`) if (isDev) { - logger.log(`[${prefix + pid}] OpenAPI: ${url}/api-docs`); + logger.log(`[${prefix + pid}] OpenAPI: ${url}/api-docs`) } - }); + }) if (module.hot) { - module.hot.accept(); - module.hot.dispose(() => app.close()); + module.hot.accept() + module.hot.dispose(() => app.close()) } } -bootstrap(); +bootstrap() diff --git a/apps/api/src/modules/auth/auth.controller.ts b/apps/api/src/modules/auth/auth.controller.ts index ca59068..a73ee13 100644 --- a/apps/api/src/modules/auth/auth.controller.ts +++ b/apps/api/src/modules/auth/auth.controller.ts @@ -1,16 +1,16 @@ -import { Body, Controller, Headers, Post, UseGuards } from '@nestjs/common'; -import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { Body, Controller, Headers, Post, UseGuards } from '@nestjs/common' +import { ApiOperation, ApiTags } from '@nestjs/swagger' -import { ApiResult } from '@/common/decorators/api-result.decorator'; -import { Ip } from '@/common/decorators/http.decorator'; +import { ApiResult } from '@/common/decorators/api-result.decorator' +import { Ip } from '@/common/decorators/http.decorator' -import { UserService } from '../user/user.service'; +import { UserService } from '../user/user.service' -import { AuthService } from './auth.service'; -import { Public } from './decorators/public.decorator'; -import { LoginDto, RegisterDto } from './dto/auth.dto'; -import { LocalGuard } from './guards/local.guard'; -import { LoginToken } from './models/auth.model'; +import { AuthService } from './auth.service' +import { Public } from './decorators/public.decorator' +import { LoginDto, RegisterDto } from './dto/auth.dto' +import { LocalGuard } from './guards/local.guard' +import { LoginToken } from './models/auth.model' @ApiTags('Auth - 认证模块') @UseGuards(LocalGuard) @@ -36,13 +36,13 @@ export class AuthController { dto.password, ip, ua, - ); - return { token }; + ) + return { token } } @Post('register') @ApiOperation({ summary: '注册' }) async register(@Body() dto: RegisterDto): Promise { - await this.userService.register(dto); + await this.userService.register(dto) } } diff --git a/apps/api/src/modules/auth/auth.module.ts b/apps/api/src/modules/auth/auth.module.ts index eb6d921..14a5e40 100644 --- a/apps/api/src/modules/auth/auth.module.ts +++ b/apps/api/src/modules/auth/auth.module.ts @@ -1,38 +1,38 @@ -import { Module } from '@nestjs/common'; +import { Module } from '@nestjs/common' -import { ConfigModule, ConfigService } from '@nestjs/config'; -import { JwtModule } from '@nestjs/jwt'; -import { PassportModule } from '@nestjs/passport'; -import { TypeOrmModule } from '@nestjs/typeorm'; +import { ConfigModule, ConfigService } from '@nestjs/config' +import { JwtModule } from '@nestjs/jwt' +import { PassportModule } from '@nestjs/passport' +import { TypeOrmModule } from '@nestjs/typeorm' -import { ISecurityConfig } from '@/config'; -import { isDev } from '@/global/env'; +import { ISecurityConfig } from '@/config' +import { isDev } from '@/global/env' -import { LogModule } from '../system/log/log.module'; -import { MenuModule } from '../system/menu/menu.module'; -import { RoleModule } from '../system/role/role.module'; -import { UserModule } from '../user/user.module'; +import { LogModule } from '../system/log/log.module' +import { MenuModule } from '../system/menu/menu.module' +import { RoleModule } from '../system/role/role.module' +import { UserModule } from '../user/user.module' -import { AuthController } from './auth.controller'; -import { AuthService } from './auth.service'; -import { AccountController } from './controllers/account.controller'; -import { CaptchaController } from './controllers/captcha.controller'; -import { EmailController } from './controllers/email.controller'; -import { AccessTokenEntity } from './entities/access-token.entity'; -import { RefreshTokenEntity } from './entities/refresh-token.entity'; -import { CaptchaService } from './services/captcha.service'; -import { TokenService } from './services/token.service'; -import { JwtStrategy } from './strategies/jwt.strategy'; -import { LocalStrategy } from './strategies/local.strategy'; +import { AuthController } from './auth.controller' +import { AuthService } from './auth.service' +import { AccountController } from './controllers/account.controller' +import { CaptchaController } from './controllers/captcha.controller' +import { EmailController } from './controllers/email.controller' +import { AccessTokenEntity } from './entities/access-token.entity' +import { RefreshTokenEntity } from './entities/refresh-token.entity' +import { CaptchaService } from './services/captcha.service' +import { TokenService } from './services/token.service' +import { JwtStrategy } from './strategies/jwt.strategy' +import { LocalStrategy } from './strategies/local.strategy' const controllers = [ AuthController, AccountController, CaptchaController, EmailController, -]; -const providers = [AuthService, TokenService, CaptchaService]; -const strategies = [LocalStrategy, JwtStrategy]; +] +const providers = [AuthService, TokenService, CaptchaService] +const strategies = [LocalStrategy, JwtStrategy] @Module({ imports: [ @@ -42,13 +42,13 @@ const strategies = [LocalStrategy, JwtStrategy]; imports: [ConfigModule], useFactory: (configService: ConfigService) => { const { jwtSecret, jwtExprire } = - configService.get('security'); + configService.get('security') return { secret: jwtSecret, expires: jwtExprire, ignoreExpiration: isDev, - }; + } }, inject: [ConfigService], }), diff --git a/apps/api/src/modules/auth/auth.service.ts b/apps/api/src/modules/auth/auth.service.ts index 44c7499..a2f4741 100644 --- a/apps/api/src/modules/auth/auth.service.ts +++ b/apps/api/src/modules/auth/auth.service.ts @@ -1,21 +1,21 @@ -import { InjectRedis } from '@liaoliaots/nestjs-redis'; -import { Injectable } from '@nestjs/common'; +import { InjectRedis } from '@liaoliaots/nestjs-redis' +import { Injectable } from '@nestjs/common' -import Redis from 'ioredis'; -import { isEmpty } from 'lodash'; +import Redis from 'ioredis' +import { isEmpty } from 'lodash' -import { BusinessException } from '@/common/exceptions/biz.exception'; -import { ErrorEnum } from '@/constants/error-code.constant'; +import { BusinessException } from '@/common/exceptions/biz.exception' +import { ErrorEnum } from '@/constants/error-code.constant' -import { UserService } from '@/modules/user/user.service'; +import { UserService } from '@/modules/user/user.service' -import { MD5 } from '@/utils'; +import { MD5 } from '@/utils' -import { LoginLogService } from '../system/log/services/login-log.service'; -import { MenuService } from '../system/menu/menu.service'; -import { RoleService } from '../system/role/role.service'; +import { LoginLogService } from '../system/log/services/login-log.service' +import { MenuService } from '../system/menu/menu.service' +import { RoleService } from '../system/role/role.service' -import { TokenService } from './services/token.service'; +import { TokenService } from './services/token.service' @Injectable() export class AuthService { @@ -29,23 +29,23 @@ export class AuthService { ) {} async validateUser(credential: string, password: string): Promise { - const user = await this.userService.findUserByUserName(credential); + const user = await this.userService.findUserByUserName(credential) if (isEmpty(user)) { - throw new BusinessException(ErrorEnum.USER_NOT_FOUND); + throw new BusinessException(ErrorEnum.USER_NOT_FOUND) } - const comparePassword = MD5(`${password}${user.psalt}`); + const comparePassword = MD5(`${password}${user.psalt}`) if (user.password !== comparePassword) { - throw new BusinessException(ErrorEnum.INVALID_USERNAME_PASSWORD); + throw new BusinessException(ErrorEnum.INVALID_USERNAME_PASSWORD) } if (user) { - const { password, ...result } = user; - return result; + const { password, ...result } = user + return result } - return null; + return null } /** @@ -58,102 +58,102 @@ export class AuthService { ip: string, ua: string, ): Promise { - const user = await this.userService.findUserByUserName(username); + const user = await this.userService.findUserByUserName(username) if (isEmpty(user)) { - throw new BusinessException(ErrorEnum.INVALID_USERNAME_PASSWORD); + throw new BusinessException(ErrorEnum.INVALID_USERNAME_PASSWORD) } - const comparePassword = MD5(`${password}${user.psalt}`); + const comparePassword = MD5(`${password}${user.psalt}`) if (user.password !== comparePassword) { - throw new BusinessException(ErrorEnum.INVALID_USERNAME_PASSWORD); + throw new BusinessException(ErrorEnum.INVALID_USERNAME_PASSWORD) } - const roleIds = await this.roleService.getRoleIdsByUser(user.id); + const roleIds = await this.roleService.getRoleIdsByUser(user.id) - const roles = await this.roleService.getRoleValues(roleIds); + const roles = await this.roleService.getRoleValues(roleIds) // 包含access_token和refresh_token - const token = await this.tokenService.generateAccessToken(user.id, roles); + const token = await this.tokenService.generateAccessToken(user.id, roles) - await this.redis.set(`auth:token:${user.id}`, token.accessToken); + await this.redis.set(`auth:token:${user.id}`, token.accessToken) // 设置密码版本号 当密码修改时,版本号+1 - await this.redis.set(`auth:passwordVersion:${user.id}`, 1); + await this.redis.set(`auth:passwordVersion:${user.id}`, 1) // 设置菜单权限 - const permissions = await this.menuService.getPermissions(user.id); - await this.setPermissionsCache(user.id, permissions); + const permissions = await this.menuService.getPermissions(user.id) + await this.setPermissionsCache(user.id, permissions) - await this.loginLogService.create(user.id, ip, ua); + await this.loginLogService.create(user.id, ip, ua) - return token.accessToken; + return token.accessToken } /** * 效验账号密码 */ async checkPassword(username: string, password: string) { - const user = await this.userService.findUserByUserName(username); + const user = await this.userService.findUserByUserName(username) - const comparePassword = MD5(`${password}${user.psalt}`); + const comparePassword = MD5(`${password}${user.psalt}`) if (user.password !== comparePassword) { - throw new BusinessException(ErrorEnum.INVALID_USERNAME_PASSWORD); + throw new BusinessException(ErrorEnum.INVALID_USERNAME_PASSWORD) } } async loginLog(uid: number, ip: string, ua: string) { - await this.loginLogService.create(uid, ip, ua); + await this.loginLogService.create(uid, ip, ua) } async logout(uid: number) { // 删除token - await this.userService.forbidden(uid); + await this.userService.forbidden(uid) } /** * 重置密码 */ async resetPassword(username: string, password: string) { - const user = await this.userService.findUserByUserName(username); + const user = await this.userService.findUserByUserName(username) - await this.userService.forceUpdatePassword(user.id, password); + await this.userService.forceUpdatePassword(user.id, password) } /** * 清除登录状态信息 */ async clearLoginStatus(uid: number): Promise { - await this.userService.forbidden(uid); + await this.userService.forbidden(uid) } /** * 获取菜单列表 */ async getMenus(uid: number): Promise { - return this.menuService.getMenus(uid); + return this.menuService.getMenus(uid) } /** * 获取权限列表 */ async getPermissions(uid: number): Promise { - return this.menuService.getPermissions(uid); + return this.menuService.getPermissions(uid) } async getPermissionsCache(uid: number): Promise { - const permissionString = await this.redis.get(`auth:permission:${uid}`); - return permissionString ? JSON.parse(permissionString) : []; + const permissionString = await this.redis.get(`auth:permission:${uid}`) + return permissionString ? JSON.parse(permissionString) : [] } async setPermissionsCache(uid: number, permissions: string[]): Promise { - await this.redis.set(`auth:permission:${uid}`, JSON.stringify(permissions)); + await this.redis.set(`auth:permission:${uid}`, JSON.stringify(permissions)) } async getPasswordVersionByUid(uid: number): Promise { - return this.redis.get(`auth:passwordVersion:${uid}`); + return this.redis.get(`auth:passwordVersion:${uid}`) } async getTokenByUid(uid: number): Promise { - return this.redis.get(`auth:token:${uid}`); + return this.redis.get(`auth:token:${uid}`) } } diff --git a/apps/api/src/modules/auth/constant.ts b/apps/api/src/modules/auth/constant.ts index fc69e65..5976d7b 100644 --- a/apps/api/src/modules/auth/constant.ts +++ b/apps/api/src/modules/auth/constant.ts @@ -1,10 +1,10 @@ -export const IS_PUBLIC_KEY = 'is_public'; +export const IS_PUBLIC_KEY = 'is_public' -export const PERMISSION_KEY = 'permission'; +export const PERMISSION_KEY = 'permission' -export const POLICY_KEY = 'policy'; +export const POLICY_KEY = 'policy' -export const ALLOW_ANON_KEY = 'allow_anon_permission'; +export const ALLOW_ANON_KEY = 'allow_anon_permission' export const AuthStrategy = { LOCAL: 'local', @@ -17,11 +17,11 @@ export const AuthStrategy = { GOOGLE: 'google', PDD: 'pdd', -} as const; +} as const export const Roles = { ADMIN: 'admin', USER: 'user', -} as const; +} as const -export type Role = (typeof Roles)[keyof typeof Roles]; +export type Role = (typeof Roles)[keyof typeof Roles] diff --git a/apps/api/src/modules/auth/controllers/account.controller.ts b/apps/api/src/modules/auth/controllers/account.controller.ts index 3631b34..622567a 100644 --- a/apps/api/src/modules/auth/controllers/account.controller.ts +++ b/apps/api/src/modules/auth/controllers/account.controller.ts @@ -1,21 +1,21 @@ -import { Body, Controller, Get, Post, Put, UseGuards } from '@nestjs/common'; -import { ApiExtraModels, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { Body, Controller, Get, Post, Put, UseGuards } from '@nestjs/common' +import { ApiExtraModels, ApiOperation, ApiTags } from '@nestjs/swagger' -import { ApiResult } from '@/common/decorators/api-result.decorator'; +import { ApiResult } from '@/common/decorators/api-result.decorator' -import { ApiSecurityAuth } from '@/common/decorators/swagger.decorator'; -import { AllowAnon } from '@/modules/auth/decorators/allow-anon.decorator'; -import { AuthUser } from '@/modules/auth/decorators/auth-user.decorator'; +import { ApiSecurityAuth } from '@/common/decorators/swagger.decorator' +import { AllowAnon } from '@/modules/auth/decorators/allow-anon.decorator' +import { AuthUser } from '@/modules/auth/decorators/auth-user.decorator' -import { MenuEntity } from '@/modules/system/menu/menu.entity'; +import { MenuEntity } from '@/modules/system/menu/menu.entity' -import { PasswordUpdateDto } from '@/modules/user/dto/password.dto'; +import { PasswordUpdateDto } from '@/modules/user/dto/password.dto' -import { AccountInfo } from '../../user/user.model'; -import { UserService } from '../../user/user.service'; -import { AuthService } from '../auth.service'; -import { AccountUpdateDto } from '../dto/account.dto'; -import { JwtAuthGuard } from '../guards/jwt-auth.guard'; +import { AccountInfo } from '../../user/user.model' +import { UserService } from '../../user/user.service' +import { AuthService } from '../auth.service' +import { AccountUpdateDto } from '../dto/account.dto' +import { JwtAuthGuard } from '../guards/jwt-auth.guard' @ApiTags('Account - 账户模块') @ApiSecurityAuth() @@ -33,14 +33,14 @@ export class AccountController { @ApiResult({ type: AccountInfo }) @AllowAnon() async profile(@AuthUser() user: IAuthUser): Promise { - return this.userService.getAccountInfo(user.uid); + return this.userService.getAccountInfo(user.uid) } @Get('logout') @ApiOperation({ summary: '账户登出' }) @AllowAnon() async logout(@AuthUser() user: IAuthUser): Promise { - await this.authService.clearLoginStatus(user.uid); + await this.authService.clearLoginStatus(user.uid) } @Get('menus') @@ -48,7 +48,7 @@ export class AccountController { @ApiResult({ type: [MenuEntity] }) @AllowAnon() async menu(@AuthUser() user: IAuthUser): Promise { - return this.authService.getMenus(user.uid); + return this.authService.getMenus(user.uid) } @Get('permissions') @@ -56,7 +56,7 @@ export class AccountController { @ApiResult({ type: [String] }) @AllowAnon() async permissions(@AuthUser() user: IAuthUser): Promise { - return this.authService.getPermissions(user.uid); + return this.authService.getPermissions(user.uid) } @Put('update') @@ -66,7 +66,7 @@ export class AccountController { @AuthUser() user: IAuthUser, @Body() dto: AccountUpdateDto, ): Promise { - await this.userService.updateAccountInfo(user.uid, dto); + await this.userService.updateAccountInfo(user.uid, dto) } @Post('password') @@ -76,6 +76,6 @@ export class AccountController { @AuthUser() user: IAuthUser, @Body() dto: PasswordUpdateDto, ): Promise { - await this.userService.updatePassword(user.uid, dto); + await this.userService.updatePassword(user.uid, dto) } } diff --git a/apps/api/src/modules/auth/controllers/captcha.controller.ts b/apps/api/src/modules/auth/controllers/captcha.controller.ts index 149324f..1b7b884 100644 --- a/apps/api/src/modules/auth/controllers/captcha.controller.ts +++ b/apps/api/src/modules/auth/controllers/captcha.controller.ts @@ -1,21 +1,21 @@ -import { InjectRedis } from '@liaoliaots/nestjs-redis'; -import { Controller, Get, Query, UseGuards } from '@nestjs/common'; -import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { InjectRedis } from '@liaoliaots/nestjs-redis' +import { Controller, Get, Query, UseGuards } from '@nestjs/common' +import { ApiOperation, ApiTags } from '@nestjs/swagger' -import { Throttle, ThrottlerGuard } from '@nestjs/throttler'; +import { Throttle, ThrottlerGuard } from '@nestjs/throttler' -import Redis from 'ioredis'; -import { isEmpty } from 'lodash'; -import * as svgCaptcha from 'svg-captcha'; +import Redis from 'ioredis' +import { isEmpty } from 'lodash' +import * as svgCaptcha from 'svg-captcha' -import { ApiResult } from '@/common/decorators/api-result.decorator'; +import { ApiResult } from '@/common/decorators/api-result.decorator' -import { generateUUID } from '@/utils'; +import { generateUUID } from '@/utils' -import { Public } from '../decorators/public.decorator'; +import { Public } from '../decorators/public.decorator' -import { ImageCaptchaDto } from '../dto/captcha.dto'; -import { ImageCaptcha } from '../models/auth.model'; +import { ImageCaptchaDto } from '../dto/captcha.dto' +import { ImageCaptcha } from '../models/auth.model' @ApiTags('Captcha - 验证码模块') @UseGuards(ThrottlerGuard) @@ -29,7 +29,7 @@ export class CaptchaController { @Public() @Throttle({ default: { limit: 2, ttl: 600000 } }) async captchaByImg(@Query() dto: ImageCaptchaDto): Promise { - const { width, height } = dto; + const { width, height } = dto const svg = svgCaptcha.create({ size: 4, @@ -38,15 +38,15 @@ export class CaptchaController { width: isEmpty(width) ? 100 : width, height: isEmpty(height) ? 50 : height, charPreset: '1234567890', - }); + }) const result = { img: `data:image/svg+xml;base64,${Buffer.from(svg.data).toString( 'base64', )}`, id: generateUUID(), - }; + } // 5分钟过期时间 - await this.redis.set(`captcha:img:${result.id}`, svg.text, 'EX', 60 * 5); - return result; + await this.redis.set(`captcha:img:${result.id}`, svg.text, 'EX', 60 * 5) + return result } } diff --git a/apps/api/src/modules/auth/controllers/email.controller.ts b/apps/api/src/modules/auth/controllers/email.controller.ts index 9eb4618..3ab54c8 100644 --- a/apps/api/src/modules/auth/controllers/email.controller.ts +++ b/apps/api/src/modules/auth/controllers/email.controller.ts @@ -1,15 +1,15 @@ -import { Body, Controller, Post, UseGuards } from '@nestjs/common'; -import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { Body, Controller, Post, UseGuards } from '@nestjs/common' +import { ApiOperation, ApiTags } from '@nestjs/swagger' -import { Throttle, ThrottlerGuard } from '@nestjs/throttler'; +import { Throttle, ThrottlerGuard } from '@nestjs/throttler' -import { Ip } from '@/common/decorators/http.decorator'; -import { MailerService } from '@/shared/mailer/mailer.service'; +import { Ip } from '@/common/decorators/http.decorator' +import { MailerService } from '@/shared/mailer/mailer.service' -import { AuthUser } from '../decorators/auth-user.decorator'; -import { Public } from '../decorators/public.decorator'; +import { AuthUser } from '../decorators/auth-user.decorator' +import { Public } from '../decorators/public.decorator' -import { SendEmailCodeDto } from '../dto/captcha.dto'; +import { SendEmailCodeDto } from '../dto/captcha.dto' @ApiTags('Auth - 认证模块') @UseGuards(ThrottlerGuard) @@ -27,12 +27,12 @@ export class EmailController { @AuthUser('uid') uid: number, ): Promise { // await this.authService.checkImgCaptcha(dto.captchaId, dto.verifyCode); - const { email } = dto; + const { email } = dto - await this.mailerService.checkLimit(email, ip); - const { code } = await this.mailerService.sendCode(email); + await this.mailerService.checkLimit(email, ip) + const { code } = await this.mailerService.sendCode(email) - await this.mailerService.log(email, code, ip); + await this.mailerService.log(email, code, ip) } // @Post() diff --git a/apps/api/src/modules/auth/decorators/allow-anon.decorator.ts b/apps/api/src/modules/auth/decorators/allow-anon.decorator.ts index f8674c3..1ff59cb 100644 --- a/apps/api/src/modules/auth/decorators/allow-anon.decorator.ts +++ b/apps/api/src/modules/auth/decorators/allow-anon.decorator.ts @@ -1,8 +1,8 @@ -import { SetMetadata } from '@nestjs/common'; +import { SetMetadata } from '@nestjs/common' -import { ALLOW_ANON_KEY } from '../constant'; +import { ALLOW_ANON_KEY } from '../constant' /** * 当接口不需要检测用户是否具有操作权限时添加该装饰器 */ -export const AllowAnon = () => SetMetadata(ALLOW_ANON_KEY, true); +export const AllowAnon = () => SetMetadata(ALLOW_ANON_KEY, true) diff --git a/apps/api/src/modules/auth/decorators/auth-user.decorator.ts b/apps/api/src/modules/auth/decorators/auth-user.decorator.ts index 1040458..8b7b58c 100644 --- a/apps/api/src/modules/auth/decorators/auth-user.decorator.ts +++ b/apps/api/src/modules/auth/decorators/auth-user.decorator.ts @@ -1,17 +1,17 @@ -import { createParamDecorator, ExecutionContext } from '@nestjs/common'; -import { FastifyRequest } from 'fastify'; +import { createParamDecorator, ExecutionContext } from '@nestjs/common' +import { FastifyRequest } from 'fastify' -type Payload = keyof IAuthUser; +type Payload = keyof IAuthUser /** * @description 获取当前登录用户信息, 并挂载到request上 */ export const AuthUser = createParamDecorator( (data: Payload, ctx: ExecutionContext) => { - const request = ctx.switchToHttp().getRequest(); + const request = ctx.switchToHttp().getRequest() // auth guard will mount this - const user = request.user as IAuthUser; + const user = request.user as IAuthUser - return data ? user?.[data] : user; + return data ? user?.[data] : user }, -); +) diff --git a/apps/api/src/modules/auth/decorators/permission.decorator.ts b/apps/api/src/modules/auth/decorators/permission.decorator.ts index a54a0a6..a0fe040 100644 --- a/apps/api/src/modules/auth/decorators/permission.decorator.ts +++ b/apps/api/src/modules/auth/decorators/permission.decorator.ts @@ -1,7 +1,7 @@ -import { applyDecorators, SetMetadata } from '@nestjs/common'; +import { applyDecorators, SetMetadata } from '@nestjs/common' -import { PERMISSION_KEY } from '../constant'; +import { PERMISSION_KEY } from '../constant' export const Permission = (permission: string | string[]) => { - return applyDecorators(SetMetadata(PERMISSION_KEY, permission)); -}; + return applyDecorators(SetMetadata(PERMISSION_KEY, permission)) +} diff --git a/apps/api/src/modules/auth/decorators/public.decorator.ts b/apps/api/src/modules/auth/decorators/public.decorator.ts index c402d38..b0ddee0 100644 --- a/apps/api/src/modules/auth/decorators/public.decorator.ts +++ b/apps/api/src/modules/auth/decorators/public.decorator.ts @@ -1,8 +1,8 @@ -import { SetMetadata } from '@nestjs/common'; +import { SetMetadata } from '@nestjs/common' -import { IS_PUBLIC_KEY } from '../constant'; +import { IS_PUBLIC_KEY } from '../constant' /** * 当接口不需要检测用户登录时添加该装饰器 */ -export const Public = () => SetMetadata(IS_PUBLIC_KEY, true); +export const Public = () => SetMetadata(IS_PUBLIC_KEY, true) diff --git a/apps/api/src/modules/auth/decorators/resource.decorator.ts b/apps/api/src/modules/auth/decorators/resource.decorator.ts index 97a0917..c4eb3ac 100644 --- a/apps/api/src/modules/auth/decorators/resource.decorator.ts +++ b/apps/api/src/modules/auth/decorators/resource.decorator.ts @@ -1,16 +1,16 @@ -import { applyDecorators, SetMetadata } from '@nestjs/common'; +import { applyDecorators, SetMetadata } from '@nestjs/common' -import { ObjectType } from 'typeorm'; +import { ObjectType } from 'typeorm' -import { POLICY_KEY } from '../constant'; +import { POLICY_KEY } from '../constant' -export type Condition = (item: T, user: IAuthUser) => boolean; +export type Condition = (item: T, user: IAuthUser) => boolean -export type ResourceObject = { entity: ObjectType; condition: Condition }; +export type ResourceObject = { entity: ObjectType; condition: Condition } export const Resource = >( entity: T, condition?: Condition, ) => { - return applyDecorators(SetMetadata(POLICY_KEY, { entity, condition })); -}; + return applyDecorators(SetMetadata(POLICY_KEY, { entity, condition })) +} diff --git a/apps/api/src/modules/auth/dto/account.dto.ts b/apps/api/src/modules/auth/dto/account.dto.ts index b59bde8..c608227 100644 --- a/apps/api/src/modules/auth/dto/account.dto.ts +++ b/apps/api/src/modules/auth/dto/account.dto.ts @@ -1,4 +1,4 @@ -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty } from '@nestjs/swagger' import { IsEmail, IsOptional, @@ -6,17 +6,17 @@ import { Matches, MaxLength, MinLength, -} from 'class-validator'; +} from 'class-validator' export class AccountUpdateDto { @ApiProperty({ description: '用户呢称' }) @IsString() @IsOptional() - nickname: string; + nickname: string @ApiProperty({ description: '用户邮箱' }) @IsEmail() - email: string; + email: string @ApiProperty({ description: '用户QQ' }) @IsOptional() @@ -24,32 +24,32 @@ export class AccountUpdateDto { @Matches(/^[0-9]+$/) @MinLength(5) @MaxLength(11) - qq: string; + qq: string @ApiProperty({ description: '用户手机号' }) @IsOptional() @IsString() - phone: string; + phone: string @ApiProperty({ description: '用户头像' }) @IsOptional() @IsString() - avatar: string; + avatar: string @ApiProperty({ description: '用户备注' }) @IsOptional() @IsString() - remark: string; + remark: string } export class ResetPasswordDto { @ApiProperty({ description: '临时token', example: 'uuid' }) @IsString() - accessToken: string; + accessToken: string @ApiProperty({ description: '密码', example: 'a123456' }) @IsString() @Matches(/^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Za-z])\S*$/) @MinLength(6) - password: string; + password: string } diff --git a/apps/api/src/modules/auth/dto/auth.dto.ts b/apps/api/src/modules/auth/dto/auth.dto.ts index fbb8f80..e9b88ac 100644 --- a/apps/api/src/modules/auth/dto/auth.dto.ts +++ b/apps/api/src/modules/auth/dto/auth.dto.ts @@ -1,33 +1,33 @@ -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty } from '@nestjs/swagger' -import { IsString, Matches, MaxLength, MinLength } from 'class-validator'; +import { IsString, Matches, MaxLength, MinLength } from 'class-validator' export class LoginDto { @ApiProperty({ description: '手机号/邮箱' }) @IsString() @MinLength(4) - username: string; + username: string @ApiProperty({ description: '密码', example: 'a123456' }) @IsString() @Matches(/^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Za-z])\S*$/) @MinLength(6) - password: string; + password: string } export class RegisterDto { @ApiProperty({ description: '账号' }) @IsString() - username: string; + username: string @ApiProperty({ description: '密码' }) @IsString() @Matches(/^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Za-z])\S*$/) @MinLength(6) @MaxLength(16) - password: string; + password: string @ApiProperty({ description: '语言', examples: ['EN', 'ZH'] }) @IsString() - lang: string; + lang: string } diff --git a/apps/api/src/modules/auth/dto/captcha.dto.ts b/apps/api/src/modules/auth/dto/captcha.dto.ts index 2bb3843..c24d3d8 100644 --- a/apps/api/src/modules/auth/dto/captcha.dto.ts +++ b/apps/api/src/modules/auth/dto/captcha.dto.ts @@ -1,12 +1,12 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; +import { ApiProperty } from '@nestjs/swagger' +import { Type } from 'class-transformer' import { IsEmail, IsInt, IsMobilePhone, IsOptional, IsString, -} from 'class-validator'; +} from 'class-validator' export class ImageCaptchaDto { @ApiProperty({ @@ -17,7 +17,7 @@ export class ImageCaptchaDto { @Type(() => Number) @IsInt() @IsOptional() - readonly width: number = 100; + readonly width: number = 100 @ApiProperty({ required: false, @@ -27,27 +27,27 @@ export class ImageCaptchaDto { @Type(() => Number) @IsInt() @IsOptional() - readonly height: number = 50; + readonly height: number = 50 } export class SendEmailCodeDto { @ApiProperty({ description: '邮箱' }) @IsEmail({}, { message: '邮箱格式不正确' }) - email: string; + email: string } export class SendSmsCodeDto { @ApiProperty({ description: '手机号' }) @IsMobilePhone('zh-CN', {}, { message: '手机号格式不正确' }) - phone: string; + phone: string } export class CheckCodeDto { @ApiProperty({ description: '手机号/邮箱' }) @IsString() - account: string; + account: string @ApiProperty({ description: '验证码' }) @IsString() - code: string; + code: string } diff --git a/apps/api/src/modules/auth/entities/access-token.entity.ts b/apps/api/src/modules/auth/entities/access-token.entity.ts index 4dd0a6c..82bc4dc 100644 --- a/apps/api/src/modules/auth/entities/access-token.entity.ts +++ b/apps/api/src/modules/auth/entities/access-token.entity.ts @@ -6,11 +6,11 @@ import { ManyToOne, OneToOne, PrimaryGeneratedColumn, -} from 'typeorm'; +} from 'typeorm' -import { UserEntity } from '@/modules/user/entities/user.entity'; +import { UserEntity } from '@/modules/user/entities/user.entity' -import { RefreshTokenEntity } from './refresh-token.entity'; +import { RefreshTokenEntity } from './refresh-token.entity' /** * 用户认证token模型 @@ -18,24 +18,24 @@ import { RefreshTokenEntity } from './refresh-token.entity'; @Entity('user_access_tokens') export class AccessTokenEntity extends BaseEntity { @PrimaryGeneratedColumn('uuid') - id!: string; + id!: string /** * @description 令牌字符串 * @type {string} */ @Column({ length: 500 }) - value!: string; + value!: string @Column({ comment: '令牌过期时间', }) - expired_at!: Date; + expired_at!: Date @CreateDateColumn({ comment: '令牌创建时间', }) - createdAt!: Date; + createdAt!: Date /** * @description 关联的刷新令牌 @@ -48,7 +48,7 @@ export class AccessTokenEntity extends BaseEntity { cascade: true, }, ) - refreshToken!: RefreshTokenEntity; + refreshToken!: RefreshTokenEntity /** * @description 所属用户 @@ -57,5 +57,5 @@ export class AccessTokenEntity extends BaseEntity { @ManyToOne(() => UserEntity, (user) => user.accessTokens, { onDelete: 'CASCADE', }) - user!: UserEntity; + user!: UserEntity } diff --git a/apps/api/src/modules/auth/entities/refresh-token.entity.ts b/apps/api/src/modules/auth/entities/refresh-token.entity.ts index 171461d..af1960a 100644 --- a/apps/api/src/modules/auth/entities/refresh-token.entity.ts +++ b/apps/api/src/modules/auth/entities/refresh-token.entity.ts @@ -6,33 +6,33 @@ import { JoinColumn, OneToOne, PrimaryGeneratedColumn, -} from 'typeorm'; +} from 'typeorm' -import { AccessTokenEntity } from './access-token.entity'; +import { AccessTokenEntity } from './access-token.entity' /** * 刷新Token的Token模型 */ @Entity('user_refresh_tokens') export class RefreshTokenEntity extends BaseEntity { @PrimaryGeneratedColumn('uuid') - id!: string; + id!: string /** * @description 令牌字符串 * @type {string} */ @Column({ length: 500 }) - value!: string; + value!: string @Column({ comment: '令牌过期时间', }) - expired_at!: Date; + expired_at!: Date @CreateDateColumn({ comment: '令牌创建时间', }) - createdAt!: Date; + createdAt!: Date /** * @description 关联的登录令牌 @@ -46,5 +46,5 @@ export class RefreshTokenEntity extends BaseEntity { }, ) @JoinColumn() - accessToken!: AccessTokenEntity; + accessToken!: AccessTokenEntity } diff --git a/apps/api/src/modules/auth/guards/jwt-auth.guard.ts b/apps/api/src/modules/auth/guards/jwt-auth.guard.ts index f222bc1..a400a13 100644 --- a/apps/api/src/modules/auth/guards/jwt-auth.guard.ts +++ b/apps/api/src/modules/auth/guards/jwt-auth.guard.ts @@ -2,18 +2,18 @@ import { ExecutionContext, Injectable, UnauthorizedException, -} from '@nestjs/common'; -import { Reflector } from '@nestjs/core'; -import { AuthGuard } from '@nestjs/passport'; -import { FastifyReply, FastifyRequest } from 'fastify'; -import { isEmpty, isNil } from 'lodash'; +} from '@nestjs/common' +import { Reflector } from '@nestjs/core' +import { AuthGuard } from '@nestjs/passport' +import { FastifyReply, FastifyRequest } from 'fastify' +import { isEmpty, isNil } from 'lodash' -import { BusinessException } from '@/common/exceptions/biz.exception'; -import { ErrorEnum } from '@/constants/error-code.constant'; -import { AuthService } from '@/modules/auth/auth.service'; -import { TokenService } from '@/modules/auth/services/token.service'; +import { BusinessException } from '@/common/exceptions/biz.exception' +import { ErrorEnum } from '@/constants/error-code.constant' +import { AuthService } from '@/modules/auth/auth.service' -import { AuthStrategy, IS_PUBLIC_KEY } from '../constant'; +import { AuthStrategy, IS_PUBLIC_KEY } from '../constant' +import { TokenService } from '../services/token.service' @Injectable() export class JwtAuthGuard extends AuthGuard(AuthStrategy.JWT) { @@ -22,43 +22,43 @@ export class JwtAuthGuard extends AuthGuard(AuthStrategy.JWT) { private authService: AuthService, private tokenService: TokenService, ) { - super(); + super() } async canActivate(context: ExecutionContext): Promise { const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [ context.getHandler(), context.getClass(), - ]); + ]) - const request = context.switchToHttp().getRequest(); - const response = context.switchToHttp().getResponse(); + const request = context.switchToHttp().getRequest() + const response = context.switchToHttp().getResponse() - const Authorization = request.headers.authorization; + const Authorization = request.headers.authorization - let result: any = false; + let result: any = false try { - result = await super.canActivate(context); + result = await super.canActivate(context) } catch (e) { // 需要后置判断 这样携带了 token 的用户就能够解析到 request.user - if (isPublic) return true; + if (isPublic) return true if (isEmpty(Authorization)) { - throw new UnauthorizedException('未登录'); + throw new UnauthorizedException('未登录') } // 判断 token 是否存在, 如果不存在则认证失败 const accessToken = isNil(Authorization) ? undefined - : await this.tokenService.checkAccessToken(Authorization!); + : await this.tokenService.checkAccessToken(Authorization!) - if (!accessToken) throw new UnauthorizedException('令牌无效'); + if (!accessToken) throw new UnauthorizedException('令牌无效') } - const pv = await this.authService.getPasswordVersionByUid(request.user.uid); + const pv = await this.authService.getPasswordVersionByUid(request.user.uid) if (pv !== `${request.user.pv}`) { // 密码版本不一致,登录期间已更改过密码 - throw new BusinessException(ErrorEnum.INVALID_LOGIN); + throw new BusinessException(ErrorEnum.INVALID_LOGIN) } // 不允许多端登录 @@ -68,14 +68,14 @@ export class JwtAuthGuard extends AuthGuard(AuthStrategy.JWT) { // throw new ApiException(ErrorEnum.CODE_1106); // } - return result; + return result } handleRequest(err, user, info) { // You can throw an exception based on either "info" or "err" arguments if (err || !user) { - throw err || new UnauthorizedException(); + throw err || new UnauthorizedException() } - return user; + return user } } diff --git a/apps/api/src/modules/auth/guards/local.guard.ts b/apps/api/src/modules/auth/guards/local.guard.ts index 028a4f1..6ffffa9 100644 --- a/apps/api/src/modules/auth/guards/local.guard.ts +++ b/apps/api/src/modules/auth/guards/local.guard.ts @@ -1,11 +1,11 @@ -import { ExecutionContext, Injectable } from '@nestjs/common'; -import { AuthGuard } from '@nestjs/passport'; +import { ExecutionContext, Injectable } from '@nestjs/common' +import { AuthGuard } from '@nestjs/passport' -import { AuthStrategy } from '../constant'; +import { AuthStrategy } from '../constant' @Injectable() export class LocalGuard extends AuthGuard(AuthStrategy.LOCAL) { async canActivate(context: ExecutionContext) { - return true; + return true } } diff --git a/apps/api/src/modules/auth/guards/rbac.guard.ts b/apps/api/src/modules/auth/guards/rbac.guard.ts index d034c36..19ff484 100644 --- a/apps/api/src/modules/auth/guards/rbac.guard.ts +++ b/apps/api/src/modules/auth/guards/rbac.guard.ts @@ -3,15 +3,15 @@ import { ExecutionContext, Injectable, UnauthorizedException, -} from '@nestjs/common'; -import { Reflector } from '@nestjs/core'; -import { FastifyRequest } from 'fastify'; +} from '@nestjs/common' +import { Reflector } from '@nestjs/core' +import { FastifyRequest } from 'fastify' -import { BusinessException } from '@/common/exceptions/biz.exception'; -import { ErrorEnum } from '@/constants/error-code.constant'; -import { AuthService } from '@/modules/auth/auth.service'; +import { BusinessException } from '@/common/exceptions/biz.exception' +import { ErrorEnum } from '@/constants/error-code.constant' +import { AuthService } from '@/modules/auth/auth.service' -import { ALLOW_ANON_KEY, PERMISSION_KEY, IS_PUBLIC_KEY } from '../constant'; +import { ALLOW_ANON_KEY, PERMISSION_KEY, IS_PUBLIC_KEY } from '../constant' @Injectable() export class RbacGuard implements CanActivate { @@ -24,57 +24,57 @@ export class RbacGuard implements CanActivate { const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [ context.getHandler(), context.getClass(), - ]); + ]) - if (isPublic) return true; + if (isPublic) return true - const request = context.switchToHttp().getRequest(); + const request = context.switchToHttp().getRequest() - const { user } = request; - if (!user) throw new UnauthorizedException('登录无效'); + const { user } = request + if (!user) throw new UnauthorizedException('登录无效') // allowAnon 是需要登录后可访问(无需权限), Public 则是无需登录也可访问. const allowAnon = this.reflector.get( ALLOW_ANON_KEY, context.getHandler(), - ); - if (allowAnon) return true; + ) + if (allowAnon) return true const payloadPermission = this.reflector.getAllAndOverride< string | string[] - >(PERMISSION_KEY, [context.getHandler(), context.getClass()]); + >(PERMISSION_KEY, [context.getHandler(), context.getClass()]) // 控制器没有设置接口权限,则默认通过 - if (!payloadPermission) return true; + if (!payloadPermission) return true - let allPermissions = await this.authService.getPermissionsCache(user.uid); + let allPermissions = await this.authService.getPermissionsCache(user.uid) // 缓存失效, 则获取新的 Permission if (!allPermissions) { - const res = await this.authService.getPermissions(user.uid); + const res = await this.authService.getPermissions(user.uid) - allPermissions = res; + allPermissions = res // set permissions into cache - await this.authService.setPermissionsCache(user.uid, allPermissions); + await this.authService.setPermissionsCache(user.uid, allPermissions) } - let canNext = false; + let canNext = false // handle permission strings if (Array.isArray(payloadPermission)) { // 只要有一个权限满足即可 - canNext = payloadPermission.every((i) => allPermissions.includes(i)); + canNext = payloadPermission.every((i) => allPermissions.includes(i)) } if (typeof payloadPermission === 'string') { - canNext = allPermissions.includes(payloadPermission); + canNext = allPermissions.includes(payloadPermission) } if (!canNext) { - throw new BusinessException(ErrorEnum.NO_PERMISSION); + throw new BusinessException(ErrorEnum.NO_PERMISSION) } - return true; + return true } } diff --git a/apps/api/src/modules/auth/guards/resource.guard.ts b/apps/api/src/modules/auth/guards/resource.guard.ts index 6ef51cd..fa592c3 100644 --- a/apps/api/src/modules/auth/guards/resource.guard.ts +++ b/apps/api/src/modules/auth/guards/resource.guard.ts @@ -1,17 +1,17 @@ -import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; -import { Reflector } from '@nestjs/core'; -import { FastifyRequest } from 'fastify'; +import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common' +import { Reflector } from '@nestjs/core' +import { FastifyRequest } from 'fastify' -import { isNil } from 'lodash'; +import { isNil } from 'lodash' -import { DataSource, Repository } from 'typeorm'; +import { DataSource, Repository } from 'typeorm' -import { BusinessException } from '@/common/exceptions/biz.exception'; -import { ErrorEnum } from '@/constants/error-code.constant'; +import { BusinessException } from '@/common/exceptions/biz.exception' +import { ErrorEnum } from '@/constants/error-code.constant' -import { POLICY_KEY, Roles, IS_PUBLIC_KEY } from '../constant'; +import { POLICY_KEY, Roles, IS_PUBLIC_KEY } from '../constant' -import { ResourceObject } from '../decorators/resource.decorator'; +import { ResourceObject } from '../decorators/resource.decorator' @Injectable() export class ResourceGuard implements CanActivate { @@ -24,62 +24,62 @@ export class ResourceGuard implements CanActivate { const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [ context.getHandler(), context.getClass(), - ]); + ]) - if (isPublic) return true; + if (isPublic) return true - const request = context.switchToHttp().getRequest(); + const request = context.switchToHttp().getRequest() - const { user } = request; + const { user } = request - if (!user) return false; + if (!user) return false // 如果是检查资源所属,且不是超级管理员,还需要进一步判断是否是自己的数据 const { entity, condition } = this.reflector.get( POLICY_KEY, context.getHandler(), - ) ?? { entity: null, condition: null }; + ) ?? { entity: null, condition: null } if (entity && !user.roles.includes(Roles.ADMIN)) { - const repo: Repository = this.dataSource.getRepository(entity); + const repo: Repository = this.dataSource.getRepository(entity) /** * 获取请求中的items,item,id,用于crud操作时验证数据 * @param request */ const getRequestItemId = (request?: FastifyRequest): number => { - const { params = {}, body = {}, query = {} } = (request ?? {}) as any; - const id = params.id ?? body.id ?? query.id; + const { params = {}, body = {}, query = {} } = (request ?? {}) as any + const id = params.id ?? body.id ?? query.id - if (!isNil(id)) return Number(id); + if (!isNil(id)) return Number(id) - return null; - }; + return null + } - const id = getRequestItemId(request); + const id = getRequestItemId(request) if (!id) { - throw new BusinessException(ErrorEnum.REQUESTED_RESOURCE_NOT_FOUND); + throw new BusinessException(ErrorEnum.REQUESTED_RESOURCE_NOT_FOUND) } - const item = await repo.findOne({ where: { id }, relations: ['user'] }); + const item = await repo.findOne({ where: { id }, relations: ['user'] }) if (!item) { - throw new BusinessException(ErrorEnum.REQUESTED_RESOURCE_NOT_FOUND); + throw new BusinessException(ErrorEnum.REQUESTED_RESOURCE_NOT_FOUND) } if (!item?.user) { - throw new BusinessException(ErrorEnum.USER_NOT_FOUND); + throw new BusinessException(ErrorEnum.USER_NOT_FOUND) } if (condition) { - return condition(item, user); + return condition(item, user) } // 如果没有设置policy,则默认只能操作自己的数据 if (item.user?.id !== user.uid) { - throw new BusinessException(ErrorEnum.REQUESTED_RESOURCE_NOT_FOUND); + throw new BusinessException(ErrorEnum.REQUESTED_RESOURCE_NOT_FOUND) } } - return true; + return true } } diff --git a/apps/api/src/modules/auth/models/auth.model.ts b/apps/api/src/modules/auth/models/auth.model.ts index 01a5e08..faeada1 100644 --- a/apps/api/src/modules/auth/models/auth.model.ts +++ b/apps/api/src/modules/auth/models/auth.model.ts @@ -1,14 +1,14 @@ -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty } from '@nestjs/swagger' export class ImageCaptcha { @ApiProperty({ description: 'base64格式的svg图片' }) - img: string; + img: string @ApiProperty({ description: '验证码对应的唯一ID' }) - id: string; + id: string } export class LoginToken { @ApiProperty({ description: 'JWT身份Token' }) - token: string; + token: string } diff --git a/apps/api/src/modules/auth/services/captcha.service.ts b/apps/api/src/modules/auth/services/captcha.service.ts index 1732c72..9a3591c 100644 --- a/apps/api/src/modules/auth/services/captcha.service.ts +++ b/apps/api/src/modules/auth/services/captcha.service.ts @@ -1,12 +1,12 @@ -import { InjectRedis } from '@liaoliaots/nestjs-redis'; -import { Injectable } from '@nestjs/common'; +import { InjectRedis } from '@liaoliaots/nestjs-redis' +import { Injectable } from '@nestjs/common' -import Redis from 'ioredis'; -import { isEmpty } from 'lodash'; +import Redis from 'ioredis' +import { isEmpty } from 'lodash' -import { BusinessException } from '@/common/exceptions/biz.exception'; -import { ErrorEnum } from '@/constants/error-code.constant'; -import { CaptchaLogService } from '@/modules/system/log/services/captcha-log.service'; +import { BusinessException } from '@/common/exceptions/biz.exception' +import { ErrorEnum } from '@/constants/error-code.constant' +import { CaptchaLogService } from '@/modules/system/log/services/captcha-log.service' @Injectable() export class CaptchaService { @@ -20,12 +20,12 @@ export class CaptchaService { * 校验图片验证码 */ async checkImgCaptcha(id: string, code: string): Promise { - const result = await this.redis.get(`captcha:img:${id}`); + const result = await this.redis.get(`captcha:img:${id}`) if (isEmpty(result) || code.toLowerCase() !== result.toLowerCase()) { - throw new BusinessException(ErrorEnum.INVALID_VERIFICATION_CODE); + throw new BusinessException(ErrorEnum.INVALID_VERIFICATION_CODE) } // 校验成功后移除验证码 - await this.redis.del(`captcha:img:${id}`); + await this.redis.del(`captcha:img:${id}`) } async log( @@ -34,6 +34,6 @@ export class CaptchaService { provider: 'sms' | 'email', uid?: number, ): Promise { - await this.captchaLogService.create(account, code, provider, uid); + await this.captchaLogService.create(account, code, provider, uid) } } diff --git a/apps/api/src/modules/auth/services/token.service.ts b/apps/api/src/modules/auth/services/token.service.ts index d8dc8df..074f25b 100644 --- a/apps/api/src/modules/auth/services/token.service.ts +++ b/apps/api/src/modules/auth/services/token.service.ts @@ -1,15 +1,15 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { JwtService } from '@nestjs/jwt'; -import dayjs from 'dayjs'; +import { Inject, Injectable } from '@nestjs/common' +import { JwtService } from '@nestjs/jwt' +import dayjs from 'dayjs' -import { ISecurityConfig, SecurityConfig } from '@/config'; -import { RoleService } from '@/modules/system/role/role.service'; -import { UserEntity } from '@/modules/user/entities/user.entity'; +import { ISecurityConfig, SecurityConfig } from '@/config' +import { RoleService } from '@/modules/system/role/role.service' +import { UserEntity } from '@/modules/user/entities/user.entity' -import { generateUUID } from '@/utils/uuid'; +import { generateUUID } from '@/utils/uuid' -import { AccessTokenEntity } from '../entities/access-token.entity'; -import { RefreshTokenEntity } from '../entities/refresh-token.entity'; +import { AccessTokenEntity } from '../entities/access-token.entity' +import { RefreshTokenEntity } from '../entities/refresh-token.entity' /** * 令牌服务 @@ -28,29 +28,29 @@ export class TokenService { * @param response */ async refreshToken(accessToken: AccessTokenEntity) { - const { user, refreshToken } = accessToken; + const { user, refreshToken } = accessToken if (refreshToken) { - const now = dayjs(); + const now = dayjs() // 判断refreshToken是否过期 - if (now.isAfter(refreshToken.expired_at)) return null; + if (now.isAfter(refreshToken.expired_at)) return null - const roleIds = await this.roleService.getRoleIdsByUser(user.id); - const roleValues = await this.roleService.getRoleValues(roleIds); + const roleIds = await this.roleService.getRoleIdsByUser(user.id) + const roleValues = await this.roleService.getRoleValues(roleIds) // 如果没过期则生成新的access_token和refresh_token - const token = await this.generateAccessToken(user.id, roleValues); + const token = await this.generateAccessToken(user.id, roleValues) - await accessToken.remove(); - return token; + await accessToken.remove() + return token } - return null; + return null } generateJwtSign(payload: any) { - const jwtSign = this.jwtService.sign(payload); + const jwtSign = this.jwtService.sign(payload) - return jwtSign; + return jwtSign } async generateAccessToken(uid: number, roles: string[] = []) { @@ -58,27 +58,27 @@ export class TokenService { uid, pv: 1, roles, - }; + } - const jwtSign = this.jwtService.sign(payload); + const jwtSign = this.jwtService.sign(payload) // 生成accessToken - const accessToken = new AccessTokenEntity(); - accessToken.value = jwtSign; - accessToken.user = { id: uid } as UserEntity; + const accessToken = new AccessTokenEntity() + accessToken.value = jwtSign + accessToken.user = { id: uid } as UserEntity accessToken.expired_at = dayjs() .add(this.securityConfig.jwtExprire, 'second') - .toDate(); + .toDate() - await accessToken.save(); + await accessToken.save() // 生成refreshToken - const refreshToken = await this.generateRefreshToken(accessToken, dayjs()); + const refreshToken = await this.generateRefreshToken(accessToken, dayjs()) return { accessToken: jwtSign, refreshToken, - }; + } } /** @@ -92,22 +92,22 @@ export class TokenService { ): Promise { const refreshTokenPayload = { uuid: generateUUID(), - }; + } const refreshTokenSign = this.jwtService.sign(refreshTokenPayload, { secret: this.securityConfig.refreshSecret, - }); + }) - const refreshToken = new RefreshTokenEntity(); - refreshToken.value = refreshTokenSign; + const refreshToken = new RefreshTokenEntity() + refreshToken.value = refreshTokenSign refreshToken.expired_at = now .add(this.securityConfig.refreshExpire, 'second') - .toDate(); - refreshToken.accessToken = accessToken; + .toDate() + refreshToken.accessToken = accessToken - await refreshToken.save(); + await refreshToken.save() - return refreshTokenSign; + return refreshTokenSign } /** @@ -119,7 +119,7 @@ export class TokenService { where: { value }, relations: ['user', 'refreshToken'], cache: true, - }); + }) } /** @@ -129,8 +129,8 @@ export class TokenService { async removeAccessToken(value: string) { const accessToken = await AccessTokenEntity.findOne({ where: { value }, - }); - if (accessToken) await accessToken.remove(); + }) + if (accessToken) await accessToken.remove() } /** @@ -141,10 +141,10 @@ export class TokenService { const refreshToken = await RefreshTokenEntity.findOne({ where: { value }, relations: ['accessToken'], - }); + }) if (refreshToken) { - if (refreshToken.accessToken) await refreshToken.accessToken.remove(); - await refreshToken.remove(); + if (refreshToken.accessToken) await refreshToken.accessToken.remove() + await refreshToken.remove() } } @@ -153,9 +153,9 @@ export class TokenService { * @param token */ async verifyAccessToken(token: string) { - const result = this.jwtService.verify(token); - if (!result) return false; + const result = this.jwtService.verify(token) + if (!result) return false - return true; + return true } } diff --git a/apps/api/src/modules/auth/strategies/jwt.strategy.ts b/apps/api/src/modules/auth/strategies/jwt.strategy.ts index 2d9c230..d311071 100644 --- a/apps/api/src/modules/auth/strategies/jwt.strategy.ts +++ b/apps/api/src/modules/auth/strategies/jwt.strategy.ts @@ -1,10 +1,10 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { PassportStrategy } from '@nestjs/passport'; -import { ExtractJwt, Strategy } from 'passport-jwt'; +import { Inject, Injectable } from '@nestjs/common' +import { PassportStrategy } from '@nestjs/passport' +import { ExtractJwt, Strategy } from 'passport-jwt' -import { ISecurityConfig, SecurityConfig } from '@/config'; +import { ISecurityConfig, SecurityConfig } from '@/config' -import { AuthStrategy } from '../constant'; +import { AuthStrategy } from '../constant' @Injectable() export class JwtStrategy extends PassportStrategy(Strategy, AuthStrategy.JWT) { @@ -15,10 +15,10 @@ export class JwtStrategy extends PassportStrategy(Strategy, AuthStrategy.JWT) { jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: securityConfig.jwtSecret, - }); + }) } async validate(payload: IAuthUser) { - return payload; + return payload } } diff --git a/apps/api/src/modules/auth/strategies/local.strategy.ts b/apps/api/src/modules/auth/strategies/local.strategy.ts index acfdfe1..ffa7bf3 100644 --- a/apps/api/src/modules/auth/strategies/local.strategy.ts +++ b/apps/api/src/modules/auth/strategies/local.strategy.ts @@ -1,9 +1,9 @@ -import { Injectable } from '@nestjs/common'; -import { PassportStrategy } from '@nestjs/passport'; -import { Strategy } from 'passport-local'; +import { Injectable } from '@nestjs/common' +import { PassportStrategy } from '@nestjs/passport' +import { Strategy } from 'passport-local' -import { AuthService } from '../auth.service'; -import { AuthStrategy } from '../constant'; +import { AuthService } from '../auth.service' +import { AuthStrategy } from '../constant' @Injectable() export class LocalStrategy extends PassportStrategy( @@ -14,11 +14,11 @@ export class LocalStrategy extends PassportStrategy( super({ usernameField: 'credential', passwordField: 'password', - }); + }) } async validate(username: string, password: string): Promise { - const user = await this.authService.validateUser(username, password); - return user; + const user = await this.authService.validateUser(username, password) + return user } } diff --git a/apps/api/src/modules/health/health.controller.ts b/apps/api/src/modules/health/health.controller.ts index 2678295..421d0bd 100644 --- a/apps/api/src/modules/health/health.controller.ts +++ b/apps/api/src/modules/health/health.controller.ts @@ -1,14 +1,14 @@ -import { Controller, Get } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; +import { Controller, Get } from '@nestjs/common' +import { ApiTags } from '@nestjs/swagger' import { HttpHealthIndicator, HealthCheck, MemoryHealthIndicator, DiskHealthIndicator, TypeOrmHealthIndicator, -} from '@nestjs/terminus'; +} from '@nestjs/terminus' -import { Permission } from '../auth/decorators/permission.decorator'; +import { Permission } from '../auth/decorators/permission.decorator' export const PermissionHealth = { NETWORK: 'app:health:network', @@ -16,7 +16,7 @@ export const PermissionHealth = { MH: 'app:health:memory-heap', MR: 'app:health:memory-rss', DISK: 'app:health:disk', -} as const; +} as const @ApiTags('Health - 健康检查') @Controller('health') @@ -32,14 +32,14 @@ export class HealthController { @HealthCheck() @Permission(PermissionHealth.NETWORK) async checkNetwork() { - return this.http.pingCheck('kuizuo', 'https://kuizuo.cn'); + return this.http.pingCheck('kuizuo', 'https://kuizuo.cn') } @Get('database') @HealthCheck() @Permission(PermissionHealth.DB) async checkDatabase() { - return this.db.pingCheck('database'); + return this.db.pingCheck('database') } @Get('memory-heap') @@ -47,7 +47,7 @@ export class HealthController { @Permission(PermissionHealth.MH) async checkMemoryHeap() { // the process should not use more than 200MB memory - return this.memory.checkHeap('memory-heap', 200 * 1024 * 1024); + return this.memory.checkHeap('memory-heap', 200 * 1024 * 1024) } @Get('memory-rss') @@ -55,7 +55,7 @@ export class HealthController { @Permission(PermissionHealth.MR) async checkMemoryRSS() { // the process should not have more than 200MB RSS memory allocated - return this.memory.checkRSS('memory-rss', 200 * 1024 * 1024); + return this.memory.checkRSS('memory-rss', 200 * 1024 * 1024) } @Get('disk') @@ -66,6 +66,6 @@ export class HealthController { // The used disk storage should not exceed 75% of the full disk size thresholdPercent: 0.75, path: '/', - }); + }) } } diff --git a/apps/api/src/modules/health/health.module.ts b/apps/api/src/modules/health/health.module.ts index e488987..141fadc 100644 --- a/apps/api/src/modules/health/health.module.ts +++ b/apps/api/src/modules/health/health.module.ts @@ -1,8 +1,8 @@ -import { HttpModule } from '@nestjs/axios'; -import { Module } from '@nestjs/common'; -import { TerminusModule } from '@nestjs/terminus'; +import { HttpModule } from '@nestjs/axios' +import { Module } from '@nestjs/common' +import { TerminusModule } from '@nestjs/terminus' -import { HealthController } from './health.controller'; +import { HealthController } from './health.controller' @Module({ imports: [TerminusModule, HttpModule], diff --git a/apps/api/src/modules/socket/admin-ws.gateway.ts b/apps/api/src/modules/socket/admin-ws.gateway.ts index f36418a..458935f 100644 --- a/apps/api/src/modules/socket/admin-ws.gateway.ts +++ b/apps/api/src/modules/socket/admin-ws.gateway.ts @@ -4,11 +4,11 @@ import { OnGatewayInit, WebSocketGateway, WebSocketServer, -} from '@nestjs/websockets'; -import { Server, Socket } from 'socket.io'; +} from '@nestjs/websockets' +import { Server, Socket } from 'socket.io' -import { AuthService } from './auth.service'; -import { EVENT_OFFLINE, EVENT_ONLINE } from './socket.event'; +import { AuthService } from './auth.service' +import { EVENT_OFFLINE, EVENT_ONLINE } from './socket.event' /** * Admin WebSokcet网关,不含权限校验,Socket端只做通知相关操作 @@ -21,10 +21,10 @@ export class AdminWSGateway implements OnGatewayConnection, OnGatewayDisconnect, OnGatewayInit { @WebSocketServer() - private wss: Server; + private wss: Server get socketServer(): Server { - return this.wss; + return this.wss } constructor(private authService: AuthService) {} @@ -42,15 +42,15 @@ export class AdminWSGateway */ async handleConnection(client: Socket): Promise { try { - this.authService.checkAdminAuthToken(client.handshake?.query?.token); + this.authService.checkAdminAuthToken(client.handshake?.query?.token) } catch (e) { // no auth - client.disconnect(); - return; + client.disconnect() + return } // broadcast online - client.broadcast.emit(EVENT_ONLINE); + client.broadcast.emit(EVENT_ONLINE) } /** @@ -58,6 +58,6 @@ export class AdminWSGateway */ async handleDisconnect(client: Socket): Promise { // TODO - client.broadcast.emit(EVENT_OFFLINE); + client.broadcast.emit(EVENT_OFFLINE) } } diff --git a/apps/api/src/modules/socket/admin-ws.guard.ts b/apps/api/src/modules/socket/admin-ws.guard.ts index 90d383d..7490a03 100644 --- a/apps/api/src/modules/socket/admin-ws.guard.ts +++ b/apps/api/src/modules/socket/admin-ws.guard.ts @@ -1,11 +1,11 @@ -import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; -import { Observable } from 'rxjs'; -import { Socket } from 'socket.io'; +import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common' +import { Observable } from 'rxjs' +import { Socket } from 'socket.io' -import { SocketException } from '@/common/exceptions/socket.exception'; -import { ErrorEnum } from '@/constants/error-code.constant'; +import { SocketException } from '@/common/exceptions/socket.exception' +import { ErrorEnum } from '@/constants/error-code.constant' -import { AuthService } from './auth.service'; +import { AuthService } from './auth.service' @Injectable() export class AdminWsGuard implements CanActivate { @@ -14,17 +14,17 @@ export class AdminWsGuard implements CanActivate { canActivate( context: ExecutionContext, ): boolean | Promise | Observable { - const client = context.switchToWs().getClient(); - const token = client?.handshake?.query?.token; + const client = context.switchToWs().getClient() + const token = client?.handshake?.query?.token try { // 挂载对象到当前请求上 - this.authService.checkAdminAuthToken(token); - return true; + this.authService.checkAdminAuthToken(token) + return true } catch (e) { // close - client.disconnect(); + client.disconnect() // 无法通过token校验 - throw new SocketException(ErrorEnum.INVALID_LOGIN); + throw new SocketException(ErrorEnum.INVALID_LOGIN) } } } diff --git a/apps/api/src/modules/socket/admin-ws.service.ts b/apps/api/src/modules/socket/admin-ws.service.ts index cfbc77e..e4a92d2 100644 --- a/apps/api/src/modules/socket/admin-ws.service.ts +++ b/apps/api/src/modules/socket/admin-ws.service.ts @@ -1,15 +1,15 @@ -import { Injectable } from '@nestjs/common'; -import { JwtService } from '@nestjs/jwt'; -import { InjectRepository } from '@nestjs/typeorm'; -import { RemoteSocket } from 'socket.io'; -import { In, Repository } from 'typeorm'; +import { Injectable } from '@nestjs/common' +import { JwtService } from '@nestjs/jwt' +import { InjectRepository } from '@nestjs/typeorm' +import { RemoteSocket } from 'socket.io' +import { In, Repository } from 'typeorm' -import { AdminWSGateway } from '@/modules/socket/admin-ws.gateway'; +import { AdminWSGateway } from '@/modules/socket/admin-ws.gateway' -import { RoleEntity } from '../system/role/role.entity'; -import { UserEntity } from '../user/entities/user.entity'; +import { RoleEntity } from '../system/role/role.entity' +import { UserEntity } from '../user/entities/user.entity' -import { EVENT_UPDATE_MENU } from './socket.event'; +import { EVENT_UPDATE_MENU } from './socket.event' @Injectable() export class AdminWSService { @@ -26,21 +26,21 @@ export class AdminWSService { * 获取当前在线用户 */ async getOnlineSockets() { - const onlineSockets = await this.adminWsGateWay.socketServer.fetchSockets(); - return onlineSockets; + const onlineSockets = await this.adminWsGateWay.socketServer.fetchSockets() + return onlineSockets } /** * 根据uid查找socketid */ async findSocketIdByUid(uid: number): Promise> { - const onlineSockets = await this.getOnlineSockets(); + const onlineSockets = await this.getOnlineSockets() const socket = onlineSockets.find((socket) => { - const token = socket.handshake.query?.token as string; - const tokenUid = this.jwtService.verify(token).uid; - return tokenUid === uid; - }); - return socket; + const token = socket.handshake.query?.token as string + const tokenUid = this.jwtService.verify(token).uid + return tokenUid === uid + }) + return socket } /** @@ -49,13 +49,13 @@ export class AdminWSService { async filterSocketIdByUidArr( uids: number[], ): Promise[]> { - const onlineSockets = await this.getOnlineSockets(); + const onlineSockets = await this.getOnlineSockets() const sockets = onlineSockets.filter((socket) => { - const token = socket.handshake.query?.token as string; - const tokenUid = this.jwtService.verify(token).uid; - return uids.includes(tokenUid); - }); - return sockets; + const token = socket.handshake.query?.token as string + const tokenUid = this.jwtService.verify(token).uid + return uids.includes(tokenUid) + }) + return sockets } /** @@ -64,13 +64,13 @@ export class AdminWSService { * @constructor */ async noticeUserToUpdateMenusByUserIds(uid: number | number[]) { - const userIds = Array.isArray(uid) ? uid : [uid]; - const sockets = await this.filterSocketIdByUidArr(userIds); + const userIds = Array.isArray(uid) ? uid : [uid] + const sockets = await this.filterSocketIdByUidArr(userIds) if (sockets) { // socket emit event this.adminWsGateWay.socketServer .to(sockets.map((n) => n.id)) - .emit(EVENT_UPDATE_MENU); + .emit(EVENT_UPDATE_MENU) } } @@ -80,9 +80,9 @@ export class AdminWSService { async noticeUserToUpdateMenusByMenuIds(menuIds: number[]): Promise { const roles = await this.roleRepo.findBy({ menus: { id: In(menuIds) }, - }); - const roleIds = roles.map((r) => r.id); - await this.noticeUserToUpdateMenusByRoleIds(roleIds); + }) + const roleIds = roles.map((r) => r.id) + await this.noticeUserToUpdateMenusByRoleIds(roleIds) } /** @@ -93,10 +93,10 @@ export class AdminWSService { roles: { id: In(roleIds), }, - }); + }) if (users) { - const userIds = users.map((n) => n.id); - await this.noticeUserToUpdateMenusByUserIds(userIds); + const userIds = users.map((n) => n.id) + await this.noticeUserToUpdateMenusByUserIds(userIds) } } } diff --git a/apps/api/src/modules/socket/auth.service.ts b/apps/api/src/modules/socket/auth.service.ts index 75290e9..77242b7 100644 --- a/apps/api/src/modules/socket/auth.service.ts +++ b/apps/api/src/modules/socket/auth.service.ts @@ -1,9 +1,9 @@ -import { Injectable } from '@nestjs/common'; -import { JwtService } from '@nestjs/jwt'; -import { isEmpty } from 'lodash'; +import { Injectable } from '@nestjs/common' +import { JwtService } from '@nestjs/jwt' +import { isEmpty } from 'lodash' -import { SocketException } from '@/common/exceptions/socket.exception'; -import { ErrorEnum } from '@/constants/error-code.constant'; +import { SocketException } from '@/common/exceptions/socket.exception' +import { ErrorEnum } from '@/constants/error-code.constant' @Injectable() export class AuthService { @@ -11,14 +11,14 @@ export class AuthService { checkAdminAuthToken(token: string | string[] | undefined): IAuthUser | never { if (isEmpty(token)) { - throw new SocketException(ErrorEnum.INVALID_LOGIN); + throw new SocketException(ErrorEnum.INVALID_LOGIN) } try { // 挂载对象到当前请求上 - return this.jwtService.verify(Array.isArray(token) ? token[0] : token); + return this.jwtService.verify(Array.isArray(token) ? token[0] : token) } catch (e) { // 无法通过token校验 - throw new SocketException(ErrorEnum.INVALID_LOGIN); + throw new SocketException(ErrorEnum.INVALID_LOGIN) } } } diff --git a/apps/api/src/modules/socket/socket.event.ts b/apps/api/src/modules/socket/socket.event.ts index 0f1e81b..67ab964 100644 --- a/apps/api/src/modules/socket/socket.event.ts +++ b/apps/api/src/modules/socket/socket.event.ts @@ -1,7 +1,7 @@ // 用户上线事件 -export const EVENT_ONLINE = 'online'; -export const EVENT_OFFLINE = 'offline'; +export const EVENT_ONLINE = 'online' +export const EVENT_OFFLINE = 'offline' // 踢下线 -export const EVENT_KICK = 'kick'; +export const EVENT_KICK = 'kick' // 当角色权限或菜单被修改时,通知用户获取最新的权限菜单 -export const EVENT_UPDATE_MENU = 'update_menu'; +export const EVENT_UPDATE_MENU = 'update_menu' diff --git a/apps/api/src/modules/socket/socket.module.ts b/apps/api/src/modules/socket/socket.module.ts index 7ae8cb5..36d3bf1 100644 --- a/apps/api/src/modules/socket/socket.module.ts +++ b/apps/api/src/modules/socket/socket.module.ts @@ -1,13 +1,13 @@ -import { Module, forwardRef } from '@nestjs/common'; +import { Module, forwardRef } from '@nestjs/common' -import { AuthModule } from '../auth/auth.module'; -import { SystemModule } from '../system/system.module'; +import { AuthModule } from '../auth/auth.module' +import { SystemModule } from '../system/system.module' -import { AdminWSGateway } from './admin-ws.gateway'; -import { AdminWSService } from './admin-ws.service'; -import { AuthService } from './auth.service'; +import { AdminWSGateway } from './admin-ws.gateway' +import { AdminWSService } from './admin-ws.service' +import { AuthService } from './auth.service' -const providers = [AdminWSGateway, AuthService, AdminWSService]; +const providers = [AdminWSGateway, AuthService, AdminWSService] /** * WebSocket Module diff --git a/apps/api/src/modules/system/dept/dept.controller.ts b/apps/api/src/modules/system/dept/dept.controller.ts index c148866..de00c3c 100644 --- a/apps/api/src/modules/system/dept/dept.controller.ts +++ b/apps/api/src/modules/system/dept/dept.controller.ts @@ -1,25 +1,17 @@ -import { - Body, - Controller, - Get, - Post, - Query, - Delete, - Put, -} from '@nestjs/common'; -import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { Body, Controller, Get, Post, Query, Delete, Put } from '@nestjs/common' +import { ApiOperation, ApiTags } from '@nestjs/swagger' -import { ApiResult } from '@/common/decorators/api-result.decorator'; -import { IdParam } from '@/common/decorators/id-param.decorator'; -import { ApiSecurityAuth } from '@/common/decorators/swagger.decorator'; -import { BusinessException } from '@/common/exceptions/biz.exception'; -import { ErrorEnum } from '@/constants/error-code.constant'; -import { AuthUser } from '@/modules/auth/decorators/auth-user.decorator'; -import { Permission } from '@/modules/auth/decorators/permission.decorator'; -import { DeptEntity } from '@/modules/system/dept/dept.entity'; +import { ApiResult } from '@/common/decorators/api-result.decorator' +import { IdParam } from '@/common/decorators/id-param.decorator' +import { ApiSecurityAuth } from '@/common/decorators/swagger.decorator' +import { BusinessException } from '@/common/exceptions/biz.exception' +import { ErrorEnum } from '@/constants/error-code.constant' +import { AuthUser } from '@/modules/auth/decorators/auth-user.decorator' +import { Permission } from '@/modules/auth/decorators/permission.decorator' +import { DeptEntity } from '@/modules/system/dept/dept.entity' -import { DeptDto, DeptQueryDto } from './dept.dto'; -import { DeptService } from './dept.service'; +import { DeptDto, DeptQueryDto } from './dept.dto' +import { DeptService } from './dept.service' export const Permissions = { LIST: 'system:dept:list', @@ -27,7 +19,7 @@ export const Permissions = { READ: 'system:dept:read', UPDATE: 'system:dept:update', DELETE: 'system:dept:delete', -} as const; +} as const @ApiSecurityAuth() @ApiTags('System - 部门模块') @@ -43,21 +35,21 @@ export class DeptController { @Query() dto: DeptQueryDto, @AuthUser('uid') uid: number, ): Promise { - return this.deptService.getDeptTree(uid, dto); + return this.deptService.getDeptTree(uid, dto) } @Post() @ApiOperation({ summary: '创建部门' }) @Permission(Permissions.CREATE) async create(@Body() dto: DeptDto): Promise { - await this.deptService.create(dto); + await this.deptService.create(dto) } @Get(':id') @ApiOperation({ summary: '查询部门信息' }) @Permission(Permissions.READ) async info(@IdParam() id: number) { - return this.deptService.info(id); + return this.deptService.info(id) } @Put(':id') @@ -67,7 +59,7 @@ export class DeptController { @IdParam() id: number, @Body() updateDeptDto: DeptDto, ): Promise { - await this.deptService.update(id, updateDeptDto); + await this.deptService.update(id, updateDeptDto) } @Delete(':id') @@ -75,16 +67,16 @@ export class DeptController { @Permission(Permissions.DELETE) async delete(@IdParam() id: number): Promise { // 查询是否有关联用户或者部门,如果含有则无法删除 - const count = await this.deptService.countUserByDeptId(id); + const count = await this.deptService.countUserByDeptId(id) if (count > 0) - throw new BusinessException(ErrorEnum.DEPARTMENT_HAS_ASSOCIATED_USERS); + throw new BusinessException(ErrorEnum.DEPARTMENT_HAS_ASSOCIATED_USERS) - const count2 = await this.deptService.countChildDept(id); + const count2 = await this.deptService.countChildDept(id) if (count2 > 0) - throw new BusinessException(ErrorEnum.DEPARTMENT_HAS_CHILD_DEPARTMENTS); + throw new BusinessException(ErrorEnum.DEPARTMENT_HAS_CHILD_DEPARTMENTS) - await this.deptService.delete(id); + await this.deptService.delete(id) } // @Post('move') diff --git a/apps/api/src/modules/system/dept/dept.dto.ts b/apps/api/src/modules/system/dept/dept.dto.ts index 551a4f0..d90710c 100644 --- a/apps/api/src/modules/system/dept/dept.dto.ts +++ b/apps/api/src/modules/system/dept/dept.dto.ts @@ -1,5 +1,5 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; +import { ApiProperty } from '@nestjs/swagger' +import { Type } from 'class-transformer' import { ArrayNotEmpty, IsArray, @@ -9,62 +9,62 @@ import { Min, MinLength, ValidateNested, -} from 'class-validator'; +} from 'class-validator' export class DeptDto { @ApiProperty({ description: '部门名称' }) @IsString() @MinLength(1) - name: string; + name: string @ApiProperty({ description: '父级部门id' }) @Type(() => Number) @IsInt() @IsOptional() - parentId: number; + parentId: number @ApiProperty({ description: '排序编号', required: false }) @IsInt() @Min(0) @IsOptional() - orderNo: number; + orderNo: number } export class TransferDeptDto { @ApiProperty({ description: '需要转移的管理员列表编号', type: [Number] }) @IsArray() @ArrayNotEmpty() - userIds: number[]; + userIds: number[] @ApiProperty({ description: '需要转移过去的系统部门ID' }) @IsInt() @Min(0) - deptId: number; + deptId: number } export class MoveDept { @ApiProperty({ description: '当前部门ID' }) @IsInt() @Min(0) - id: number; + id: number @ApiProperty({ description: '移动到指定父级部门的ID' }) @IsInt() @Min(0) @IsOptional() - parentId: number; + parentId: number } export class MoveDeptDto { @ApiProperty({ description: '部门列表', type: [MoveDept] }) @ValidateNested({ each: true }) @Type(() => MoveDept) - depts: MoveDept[]; + depts: MoveDept[] } export class DeptQueryDto { @ApiProperty({ description: '部门名称' }) @IsString() @IsOptional() - name: string; + name: string } diff --git a/apps/api/src/modules/system/dept/dept.entity.ts b/apps/api/src/modules/system/dept/dept.entity.ts index a47913d..9d914eb 100644 --- a/apps/api/src/modules/system/dept/dept.entity.ts +++ b/apps/api/src/modules/system/dept/dept.entity.ts @@ -1,4 +1,4 @@ -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty } from '@nestjs/swagger' import { Column, Entity, @@ -6,29 +6,29 @@ import { Tree, TreeChildren, TreeParent, -} from 'typeorm'; +} from 'typeorm' -import { AbstractEntity } from '@/common/entity/abstract.entity'; +import { AbstractEntity } from '@/common/entity/abstract.entity' -import { UserEntity } from '../../user/entities/user.entity'; +import { UserEntity } from '../../user/entities/user.entity' @Entity({ name: 'sys_dept' }) @Tree('materialized-path') export class DeptEntity extends AbstractEntity { @Column() @ApiProperty({ description: '部门名称' }) - name: string; + name: string @Column({ nullable: true, default: 0 }) @ApiProperty({ description: '排序' }) - orderNo: number; + orderNo: number @TreeChildren({ cascade: true }) - children!: DeptEntity[]; + children!: DeptEntity[] @TreeParent({ onDelete: 'SET NULL' }) - parent?: DeptEntity | null; + parent?: DeptEntity | null @OneToMany(() => UserEntity, (user) => user.dept) - users: UserEntity[]; + users: UserEntity[] } diff --git a/apps/api/src/modules/system/dept/dept.module.ts b/apps/api/src/modules/system/dept/dept.module.ts index aeade9c..d936ed9 100644 --- a/apps/api/src/modules/system/dept/dept.module.ts +++ b/apps/api/src/modules/system/dept/dept.module.ts @@ -1,14 +1,14 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; +import { Module } from '@nestjs/common' +import { TypeOrmModule } from '@nestjs/typeorm' -import { UserModule } from '../../user/user.module'; -import { RoleModule } from '../role/role.module'; +import { UserModule } from '../../user/user.module' +import { RoleModule } from '../role/role.module' -import { DeptController } from './dept.controller'; -import { DeptEntity } from './dept.entity'; -import { DeptService } from './dept.service'; +import { DeptController } from './dept.controller' +import { DeptEntity } from './dept.entity' +import { DeptService } from './dept.service' -const services = [DeptService]; +const services = [DeptService] @Module({ imports: [TypeOrmModule.forFeature([DeptEntity]), UserModule, RoleModule], diff --git a/apps/api/src/modules/system/dept/dept.service.ts b/apps/api/src/modules/system/dept/dept.service.ts index ff5d7a5..7482d2a 100644 --- a/apps/api/src/modules/system/dept/dept.service.ts +++ b/apps/api/src/modules/system/dept/dept.service.ts @@ -1,18 +1,18 @@ -import { Injectable } from '@nestjs/common'; -import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm'; -import { isEmpty } from 'lodash'; -import { EntityManager, Repository, TreeRepository } from 'typeorm'; +import { Injectable } from '@nestjs/common' +import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm' +import { isEmpty } from 'lodash' +import { EntityManager, Repository, TreeRepository } from 'typeorm' -import { BusinessException } from '@/common/exceptions/biz.exception'; -import { ErrorEnum } from '@/constants/error-code.constant'; -import { DeptEntity } from '@/modules/system/dept/dept.entity'; -import { UserEntity } from '@/modules/user/entities/user.entity'; +import { BusinessException } from '@/common/exceptions/biz.exception' +import { ErrorEnum } from '@/constants/error-code.constant' +import { DeptEntity } from '@/modules/system/dept/dept.entity' +import { UserEntity } from '@/modules/user/entities/user.entity' -import { deleteEmptyChildren } from '@/utils/list2tree'; +import { deleteEmptyChildren } from '@/utils/list2tree' -import { RoleService } from '../role/role.service'; +import { RoleService } from '../role/role.service' -import { MoveDept, DeptDto, DeptQueryDto } from './dept.dto'; +import { MoveDept, DeptDto, DeptQueryDto } from './dept.dto' @Injectable() export class DeptService { @@ -26,7 +26,7 @@ export class DeptService { ) {} async list(): Promise { - return this.deptRepository.find({ order: { orderNo: 'DESC' } }); + return this.deptRepository.find({ order: { orderNo: 'DESC' } }) } async info(id: number): Promise { @@ -34,46 +34,46 @@ export class DeptService { .createQueryBuilder('dept') .leftJoinAndSelect('dept.parent', 'parent') .where({ id }) - .getOne(); + .getOne() if (isEmpty(dept)) { - throw new BusinessException(ErrorEnum.DEPARTMENT_NOT_FOUND); + throw new BusinessException(ErrorEnum.DEPARTMENT_NOT_FOUND) } - return dept; + return dept } async create({ parentId, ...data }: DeptDto): Promise { const parent = await this.deptRepository .createQueryBuilder('dept') .where({ id: parentId }) - .getOne(); + .getOne() await this.deptRepository.save({ ...data, parent, - }); + }) } async update(id: number, { parentId, ...data }: DeptDto): Promise { const item = await this.deptRepository .createQueryBuilder('dept') .where({ id }) - .getOne(); + .getOne() const parent = await this.deptRepository .createQueryBuilder('dept') .where({ id: parentId }) - .getOne(); + .getOne() await this.deptRepository.save({ ...item, ...data, parent, - }); + }) } async delete(id: number): Promise { - await this.deptRepository.delete(id); + await this.deptRepository.delete(id) } /** @@ -81,23 +81,23 @@ export class DeptService { */ async move(depts: MoveDept[]): Promise { await this.entityManager.transaction(async (manager) => { - await manager.save(depts); - }); + await manager.save(depts) + }) } /** * 根据部门查询关联的用户数量 */ async countUserByDeptId(id: number): Promise { - return this.userRepository.countBy({ dept: { id } }); + return this.userRepository.countBy({ dept: { id } }) } /** * 查找当前部门下的子部门数量 */ async countChildDept(id: number): Promise { - const item = await this.deptRepository.findOneBy({ id }); - return this.deptRepository.countDescendants(item); + const item = await this.deptRepository.findOneBy({ id }) + return this.deptRepository.countDescendants(item) } /** @@ -107,31 +107,31 @@ export class DeptService { uid: number, { name }: DeptQueryDto, ): Promise { - const tree: DeptEntity[] = []; + const tree: DeptEntity[] = [] if (name) { const deptList = await this.deptRepository .createQueryBuilder('dept') .where('dept.name like :name', { name: `%${name}%` }) - .getMany(); + .getMany() for (const dept of deptList) { - const deptTree = await this.deptRepository.findDescendantsTree(dept); - tree.push(deptTree); + const deptTree = await this.deptRepository.findDescendantsTree(dept) + tree.push(deptTree) } - deleteEmptyChildren(tree); + deleteEmptyChildren(tree) - return tree; + return tree } const deptTree = await this.deptRepository.findTrees({ depth: 2, relations: ['parent'], - }); + }) - deleteEmptyChildren(deptTree); + deleteEmptyChildren(deptTree) - return deptTree; + return deptTree } } diff --git a/apps/api/src/modules/system/dict/dict.controller.ts b/apps/api/src/modules/system/dict/dict.controller.ts index d4b6d5f..84352d9 100644 --- a/apps/api/src/modules/system/dict/dict.controller.ts +++ b/apps/api/src/modules/system/dict/dict.controller.ts @@ -1,15 +1,15 @@ -import { Body, Controller, Delete, Get, Post, Query } from '@nestjs/common'; -import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { Body, Controller, Delete, Get, Post, Query } from '@nestjs/common' +import { ApiOperation, ApiTags } from '@nestjs/swagger' -import { ApiResult } from '@/common/decorators/api-result.decorator'; -import { IdParam } from '@/common/decorators/id-param.decorator'; -import { ApiSecurityAuth } from '@/common/decorators/swagger.decorator'; -import { Pagination } from '@/helper/paginate/pagination'; -import { Permission } from '@/modules/auth/decorators/permission.decorator'; -import { DictEntity } from '@/modules/system/dict/dict.entity'; +import { ApiResult } from '@/common/decorators/api-result.decorator' +import { IdParam } from '@/common/decorators/id-param.decorator' +import { ApiSecurityAuth } from '@/common/decorators/swagger.decorator' +import { Pagination } from '@/helper/paginate/pagination' +import { Permission } from '@/modules/auth/decorators/permission.decorator' +import { DictEntity } from '@/modules/system/dict/dict.entity' -import { DictDto, DictQueryDto } from './dict.dto'; -import { DictService } from './dict.service'; +import { DictDto, DictQueryDto } from './dict.dto' +import { DictService } from './dict.service' export const Permissions = { LIST: 'system:dict:list', @@ -17,7 +17,7 @@ export const Permissions = { READ: 'system:dict:read', UPDATE: 'system:dict:update', DELETE: 'system:dict:delete', -} as const; +} as const @ApiTags('System - 字典配置模块') @ApiSecurityAuth() @@ -30,15 +30,15 @@ export class DictController { @ApiResult({ type: [DictEntity] }) @Permission(Permissions.LIST) async list(@Query() dto: DictQueryDto): Promise> { - return this.dictService.page(dto); + return this.dictService.page(dto) } @Post() @ApiOperation({ summary: '新增字典配置' }) @Permission(Permissions.CREATE) async create(@Body() dto: DictDto): Promise { - await this.dictService.isExistKey(dto.key); - await this.dictService.create(dto); + await this.dictService.isExistKey(dto.key) + await this.dictService.create(dto) } @Get(':id') @@ -46,20 +46,20 @@ export class DictController { @ApiResult({ type: DictEntity }) @Permission(Permissions.READ) async info(@IdParam() id: number): Promise { - return this.dictService.findOne(id); + return this.dictService.findOne(id) } @Post(':id') @ApiOperation({ summary: '更新字典配置' }) @Permission(Permissions.UPDATE) async update(@IdParam() id: number, @Body() dto: DictDto): Promise { - await this.dictService.update(id, dto); + await this.dictService.update(id, dto) } @Delete(':id') @ApiOperation({ summary: '删除指定的字典配置' }) @Permission(Permissions.DELETE) async delete(@IdParam() id: number): Promise { - await this.dictService.delete(id); + await this.dictService.delete(id) } } diff --git a/apps/api/src/modules/system/dict/dict.dto.ts b/apps/api/src/modules/system/dict/dict.dto.ts index 6a17f12..2904b51 100644 --- a/apps/api/src/modules/system/dict/dict.dto.ts +++ b/apps/api/src/modules/system/dict/dict.dto.ts @@ -1,31 +1,31 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsOptional, IsString, MinLength } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger' +import { IsOptional, IsString, MinLength } from 'class-validator' -import { PageOptionsDto } from '@/common/dto/page-options.dto'; +import { PageOptionsDto } from '@/common/dto/page-options.dto' export class DictDto { @ApiProperty({ description: '参数名称' }) @IsString() - name: string; + name: string @ApiProperty({ description: '参数键名' }) @IsString() @MinLength(3) - key: string; + key: string @ApiProperty({ description: '参数值' }) @IsString() - value: string; + value: string @ApiProperty({ description: '备注' }) @IsOptional() @IsString() - remark?: string; + remark?: string } export class DictQueryDto extends PageOptionsDto { @ApiProperty({ description: '参数名称' }) @IsString() @IsOptional() - name: string; + name: string } diff --git a/apps/api/src/modules/system/dict/dict.entity.ts b/apps/api/src/modules/system/dict/dict.entity.ts index 27403f3..9fcc95e 100644 --- a/apps/api/src/modules/system/dict/dict.entity.ts +++ b/apps/api/src/modules/system/dict/dict.entity.ts @@ -1,27 +1,27 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { PrimaryGeneratedColumn, Column, Entity } from 'typeorm'; +import { ApiProperty } from '@nestjs/swagger' +import { PrimaryGeneratedColumn, Column, Entity } from 'typeorm' -import { AbstractEntity } from '@/common/entity/abstract.entity'; +import { AbstractEntity } from '@/common/entity/abstract.entity' @Entity({ name: 'sys_config' }) export class DictEntity extends AbstractEntity { @PrimaryGeneratedColumn() @ApiProperty() - id: number; + id: number @Column({ type: 'varchar', length: 50 }) @ApiProperty({ description: '配置名' }) - name: string; + name: string @Column({ type: 'varchar', length: 50, unique: true }) @ApiProperty({ description: '配置键名' }) - key: string; + key: string @Column({ type: 'varchar', nullable: true }) @ApiProperty({ description: '配置值' }) - value: string; + value: string @Column({ type: 'varchar', nullable: true }) @ApiProperty({ description: '配置描述' }) - remark: string; + remark: string } diff --git a/apps/api/src/modules/system/dict/dict.module.ts b/apps/api/src/modules/system/dict/dict.module.ts index f7daffd..39bd216 100644 --- a/apps/api/src/modules/system/dict/dict.module.ts +++ b/apps/api/src/modules/system/dict/dict.module.ts @@ -1,11 +1,11 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; +import { Module } from '@nestjs/common' +import { TypeOrmModule } from '@nestjs/typeorm' -import { DictController } from './dict.controller'; -import { DictEntity } from './dict.entity'; -import { DictService } from './dict.service'; +import { DictController } from './dict.controller' +import { DictEntity } from './dict.entity' +import { DictService } from './dict.service' -const services = [DictService]; +const services = [DictService] @Module({ imports: [TypeOrmModule.forFeature([DictEntity])], diff --git a/apps/api/src/modules/system/dict/dict.service.ts b/apps/api/src/modules/system/dict/dict.service.ts index a8abbf7..b72399e 100644 --- a/apps/api/src/modules/system/dict/dict.service.ts +++ b/apps/api/src/modules/system/dict/dict.service.ts @@ -1,15 +1,15 @@ -import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; +import { Injectable } from '@nestjs/common' +import { InjectRepository } from '@nestjs/typeorm' -import { Repository } from 'typeorm'; +import { Repository } from 'typeorm' -import { BusinessException } from '@/common/exceptions/biz.exception'; -import { ErrorEnum } from '@/constants/error-code.constant'; -import { paginate } from '@/helper/paginate'; -import { Pagination } from '@/helper/paginate/pagination'; -import { DictEntity } from '@/modules/system/dict/dict.entity'; +import { BusinessException } from '@/common/exceptions/biz.exception' +import { ErrorEnum } from '@/constants/error-code.constant' +import { paginate } from '@/helper/paginate' +import { Pagination } from '@/helper/paginate/pagination' +import { DictEntity } from '@/modules/system/dict/dict.entity' -import { DictDto, DictQueryDto } from './dict.dto'; +import { DictDto, DictQueryDto } from './dict.dto' @Injectable() export class DictService { @@ -26,56 +26,56 @@ export class DictService { pageSize, name, }: DictQueryDto): Promise> { - const queryBuilder = this.dictRepository.createQueryBuilder('dict'); + const queryBuilder = this.dictRepository.createQueryBuilder('dict') if (name) { queryBuilder.where('dict.name LIKE :name', { name: `%${name}%`, - }); + }) } - return paginate(queryBuilder, { page, pageSize }); + return paginate(queryBuilder, { page, pageSize }) } /** * 获取参数总数 */ async countConfigList(): Promise { - return this.dictRepository.count(); + return this.dictRepository.count() } /** * 新增 */ async create(dto: DictDto): Promise { - await this.dictRepository.insert(dto); + await this.dictRepository.insert(dto) } /** * 更新 */ async update(id: number, dto: Partial): Promise { - await this.dictRepository.update(id, dto); + await this.dictRepository.update(id, dto) } /** * 删除 */ async delete(id: number): Promise { - await this.dictRepository.delete(id); + await this.dictRepository.delete(id) } /** * 查询单个 */ async findOne(id: number): Promise { - return this.dictRepository.findOneBy({ id }); + return this.dictRepository.findOneBy({ id }) } async isExistKey(key: string): Promise { - const result = await this.dictRepository.findOneBy({ key }); + const result = await this.dictRepository.findOneBy({ key }) if (result) { - throw new BusinessException(ErrorEnum.PARAMETER_CONFIG_KEY_EXISTS); + throw new BusinessException(ErrorEnum.PARAMETER_CONFIG_KEY_EXISTS) } } @@ -83,10 +83,10 @@ export class DictService { const result = await this.dictRepository.findOne({ where: { key }, select: ['value'], - }); + }) if (result) { - return result.value; + return result.value } - return null; + return null } } diff --git a/apps/api/src/modules/system/log/dto/log.dto.ts b/apps/api/src/modules/system/log/dto/log.dto.ts index 01d9591..d9cef6c 100644 --- a/apps/api/src/modules/system/log/dto/log.dto.ts +++ b/apps/api/src/modules/system/log/dto/log.dto.ts @@ -1,57 +1,57 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsOptional, IsString } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger' +import { IsOptional, IsString } from 'class-validator' -import { PageOptionsDto } from '@/common/dto/page-options.dto'; +import { PageOptionsDto } from '@/common/dto/page-options.dto' export class LoginLogQueryDto extends PageOptionsDto { @ApiProperty({ description: '用户名' }) @IsString() @IsOptional() - username: string; + username: string @ApiProperty({ description: '登录IP' }) @IsOptional() @IsString() - ip?: string; + ip?: string @ApiProperty({ description: '登录地点' }) @IsOptional() @IsString() - address?: string; + address?: string @ApiProperty({ description: '登录时间' }) @IsOptional() - time?: string[]; + time?: string[] } export class TaskLogQueryDto extends PageOptionsDto { @ApiProperty({ description: '用户名' }) @IsOptional() @IsString() - username: string; + username: string @ApiProperty({ description: '登录IP' }) @IsString() @IsOptional() - ip?: string; + ip?: string @ApiProperty({ description: '登录时间' }) @IsOptional() - time?: string[]; + time?: string[] } export class CaptchaLogQueryDto extends PageOptionsDto { @ApiProperty({ description: '用户名' }) @IsOptional() @IsString() - username: string; + username: string @ApiProperty({ description: '验证码' }) @IsString() @IsOptional() - code?: string; + code?: string @ApiProperty({ description: '发送时间' }) @IsOptional() - time?: string[]; + time?: string[] } diff --git a/apps/api/src/modules/system/log/entities/captcha-log.entity.ts b/apps/api/src/modules/system/log/entities/captcha-log.entity.ts index 1ed0923..d185828 100644 --- a/apps/api/src/modules/system/log/entities/captcha-log.entity.ts +++ b/apps/api/src/modules/system/log/entities/captcha-log.entity.ts @@ -1,27 +1,27 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { Column, Entity } from 'typeorm'; +import { ApiProperty } from '@nestjs/swagger' +import { Column, Entity } from 'typeorm' -import { AbstractEntity } from '@/common/entity/abstract.entity'; +import { AbstractEntity } from '@/common/entity/abstract.entity' @Entity({ name: 'sys_captcha_log' }) export class CaptchaLogEntity extends AbstractEntity { @Column({ name: 'user_id', nullable: true }) @ApiProperty({ description: '用户ID' }) - userId: number; + userId: number @Column({ nullable: true }) @ApiProperty({ description: '账号' }) - account: string; + account: string @Column({ nullable: true }) @ApiProperty({ description: '验证码' }) - code: string; + code: string @Column({ nullable: true }) @ApiProperty({ description: '验证码提供方' }) - provider: 'sms' | 'email'; + provider: 'sms' | 'email' @Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP' }) @ApiProperty({ description: '发送时间' }) - time: Date; + time: Date } diff --git a/apps/api/src/modules/system/log/entities/login-log.entity.ts b/apps/api/src/modules/system/log/entities/login-log.entity.ts index 3c7955d..6681ab6 100644 --- a/apps/api/src/modules/system/log/entities/login-log.entity.ts +++ b/apps/api/src/modules/system/log/entities/login-log.entity.ts @@ -1,33 +1,33 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm'; +import { ApiProperty } from '@nestjs/swagger' +import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm' -import { AbstractEntity } from '@/common/entity/abstract.entity'; +import { AbstractEntity } from '@/common/entity/abstract.entity' -import { UserEntity } from '../../../user/entities/user.entity'; +import { UserEntity } from '../../../user/entities/user.entity' @Entity({ name: 'sys_login_log' }) export class LoginLogEntity extends AbstractEntity { @Column({ nullable: true }) @ApiProperty({ description: 'IP' }) - ip: string; + ip: string @Column({ nullable: true }) @ApiProperty({ description: '地址' }) - address: string; + address: string @Column({ nullable: true }) @ApiProperty({ description: '登录方式' }) - provider: string; + provider: string @Column({ type: 'datetime', nullable: true }) @ApiProperty({ description: '登录时间' }) - time: Date; + time: Date @Column({ length: 500, nullable: true }) @ApiProperty({ description: '浏览器ua' }) - ua: string; + ua: string @ManyToOne(() => UserEntity) @JoinColumn() - user: UserEntity; + user: UserEntity } diff --git a/apps/api/src/modules/system/log/entities/task-log.entity.ts b/apps/api/src/modules/system/log/entities/task-log.entity.ts index c68cd90..7246aa8 100644 --- a/apps/api/src/modules/system/log/entities/task-log.entity.ts +++ b/apps/api/src/modules/system/log/entities/task-log.entity.ts @@ -1,25 +1,25 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm'; +import { ApiProperty } from '@nestjs/swagger' +import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm' -import { AbstractEntity } from '@/common/entity/abstract.entity'; +import { AbstractEntity } from '@/common/entity/abstract.entity' -import { TaskEntity } from '../../task/task.entity'; +import { TaskEntity } from '../../task/task.entity' @Entity({ name: 'sys_task_log' }) export class TaskLogEntity extends AbstractEntity { @Column({ type: 'tinyint', default: 0 }) @ApiProperty({ description: '任务状态:0失败,1成功' }) - status: number; + status: number @Column({ type: 'text', nullable: true }) @ApiProperty({ description: '任务日志信息' }) - detail: string; + detail: string @Column({ type: 'int', nullable: true, name: 'consume_time', default: 0 }) @ApiProperty({ description: '任务耗时' }) - consumeTime: number; + consumeTime: number @ManyToOne(() => TaskEntity) @JoinColumn() - task: TaskEntity; + task: TaskEntity } diff --git a/apps/api/src/modules/system/log/log.controller.ts b/apps/api/src/modules/system/log/log.controller.ts index 34a0c20..011b62a 100644 --- a/apps/api/src/modules/system/log/log.controller.ts +++ b/apps/api/src/modules/system/log/log.controller.ts @@ -1,29 +1,29 @@ -import { Controller, Get, Query } from '@nestjs/common'; -import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { Controller, Get, Query } from '@nestjs/common' +import { ApiOperation, ApiTags } from '@nestjs/swagger' -import { ApiResult } from '@/common/decorators/api-result.decorator'; -import { ApiSecurityAuth } from '@/common/decorators/swagger.decorator'; -import { Pagination } from '@/helper/paginate/pagination'; +import { ApiResult } from '@/common/decorators/api-result.decorator' +import { ApiSecurityAuth } from '@/common/decorators/swagger.decorator' +import { Pagination } from '@/helper/paginate/pagination' -import { Permission } from '@/modules/auth/decorators/permission.decorator'; +import { Permission } from '@/modules/auth/decorators/permission.decorator' import { CaptchaLogQueryDto, LoginLogQueryDto, TaskLogQueryDto, -} from './dto/log.dto'; -import { CaptchaLogEntity } from './entities/captcha-log.entity'; -import { TaskLogEntity } from './entities/task-log.entity'; -import { LoginLogInfo } from './models/log.model'; -import { CaptchaLogService } from './services/captcha-log.service'; -import { LoginLogService } from './services/login-log.service'; -import { TaskLogService } from './services/task-log.service'; +} from './dto/log.dto' +import { CaptchaLogEntity } from './entities/captcha-log.entity' +import { TaskLogEntity } from './entities/task-log.entity' +import { LoginLogInfo } from './models/log.model' +import { CaptchaLogService } from './services/captcha-log.service' +import { LoginLogService } from './services/login-log.service' +import { TaskLogService } from './services/task-log.service' export const Permissions = { TaskList: 'system:log:task:list', LogList: 'system:log:login:list', CaptchaList: 'system:log:captcha:list', -}; +} @ApiSecurityAuth() @ApiTags('System - 日志模块') @@ -42,7 +42,7 @@ export class LogController { async loginLogPage( @Query() dto: LoginLogQueryDto, ): Promise> { - return this.loginLogService.list(dto); + return this.loginLogService.list(dto) } @Get('task/list') @@ -50,7 +50,7 @@ export class LogController { @ApiResult({ type: [TaskLogEntity], isPage: true }) @Permission(Permissions.LogList) async taskList(@Query() dto: TaskLogQueryDto) { - return this.taskService.list(dto); + return this.taskService.list(dto) } @Get('captcha/list') @@ -60,6 +60,6 @@ export class LogController { async captchaList( @Query() dto: CaptchaLogQueryDto, ): Promise> { - return this.captchaLogService.paginate(dto); + return this.captchaLogService.paginate(dto) } } diff --git a/apps/api/src/modules/system/log/log.module.ts b/apps/api/src/modules/system/log/log.module.ts index 7f1b5b8..27f9f04 100644 --- a/apps/api/src/modules/system/log/log.module.ts +++ b/apps/api/src/modules/system/log/log.module.ts @@ -1,17 +1,17 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; +import { Module } from '@nestjs/common' +import { TypeOrmModule } from '@nestjs/typeorm' -import { UserModule } from '../../user/user.module'; +import { UserModule } from '../../user/user.module' -import { CaptchaLogEntity } from './entities/captcha-log.entity'; -import { LoginLogEntity } from './entities/login-log.entity'; -import { TaskLogEntity } from './entities/task-log.entity'; -import { LogController } from './log.controller'; -import { CaptchaLogService } from './services/captcha-log.service'; -import { LoginLogService } from './services/login-log.service'; -import { TaskLogService } from './services/task-log.service'; +import { CaptchaLogEntity } from './entities/captcha-log.entity' +import { LoginLogEntity } from './entities/login-log.entity' +import { TaskLogEntity } from './entities/task-log.entity' +import { LogController } from './log.controller' +import { CaptchaLogService } from './services/captcha-log.service' +import { LoginLogService } from './services/login-log.service' +import { TaskLogService } from './services/task-log.service' -const providers = [LoginLogService, TaskLogService, CaptchaLogService]; +const providers = [LoginLogService, TaskLogService, CaptchaLogService] @Module({ imports: [ diff --git a/apps/api/src/modules/system/log/models/log.model.ts b/apps/api/src/modules/system/log/models/log.model.ts index 2ac25a5..d93f29e 100644 --- a/apps/api/src/modules/system/log/models/log.model.ts +++ b/apps/api/src/modules/system/log/models/log.model.ts @@ -1,47 +1,47 @@ -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty } from '@nestjs/swagger' export class LoginLogInfo { @ApiProperty({ description: '日志编号' }) - id: number; + id: number @ApiProperty({ description: '登录ip', example: '1.1.1.1' }) - ip: string; + ip: string @ApiProperty({ description: '登录地址' }) - address: string; + address: string @ApiProperty({ description: '系统', example: 'Windows 10' }) - os: string; + os: string @ApiProperty({ description: '浏览器', example: 'Chrome' }) - browser: string; + browser: string @ApiProperty({ description: '时间', example: '2022-01-01 00:00:00' }) - time: string; + time: string @ApiProperty({ description: '登录用户名', example: 'admin' }) - username: string; + username: string } export class TaskLogInfo { @ApiProperty({ description: '日志编号' }) - id: number; + id: number @ApiProperty({ description: '任务编号' }) - taskId: number; + taskId: number @ApiProperty({ description: '任务名称' }) - name: string; + name: string @ApiProperty({ description: '创建时间' }) - createdAt: string; + createdAt: string @ApiProperty({ description: '耗时' }) - consumeTime: number; + consumeTime: number @ApiProperty({ description: '执行信息' }) - detail: string; + detail: string @ApiProperty({ description: '任务执行状态' }) - status: number; + status: number } diff --git a/apps/api/src/modules/system/log/services/captcha-log.service.ts b/apps/api/src/modules/system/log/services/captcha-log.service.ts index 7d6890e..d710c3c 100644 --- a/apps/api/src/modules/system/log/services/captcha-log.service.ts +++ b/apps/api/src/modules/system/log/services/captcha-log.service.ts @@ -1,12 +1,12 @@ -import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; +import { Injectable } from '@nestjs/common' +import { InjectRepository } from '@nestjs/typeorm' -import { LessThan, Repository } from 'typeorm'; +import { LessThan, Repository } from 'typeorm' -import { paginate } from '@/helper/paginate'; +import { paginate } from '@/helper/paginate' -import { CaptchaLogQueryDto } from '../dto/log.dto'; -import { CaptchaLogEntity } from '../entities/captcha-log.entity'; +import { CaptchaLogQueryDto } from '../dto/log.dto' +import { CaptchaLogEntity } from '../entities/captcha-log.entity' @Injectable() export class CaptchaLogService { @@ -26,25 +26,25 @@ export class CaptchaLogService { code, provider, userId: uid, - }); + }) } async paginate({ page, pageSize }: CaptchaLogQueryDto) { const queryBuilder = await this.captchaLogRepository .createQueryBuilder('captcha_log') - .orderBy('captcha_log.id', 'DESC'); + .orderBy('captcha_log.id', 'DESC') return paginate(queryBuilder, { page, pageSize, - }); + }) } async clearLog(): Promise { - await this.captchaLogRepository.clear(); + await this.captchaLogRepository.clear() } async clearLogBeforeTime(time: Date): Promise { - await this.captchaLogRepository.delete({ createdAt: LessThan(time) }); + await this.captchaLogRepository.delete({ createdAt: LessThan(time) }) } } diff --git a/apps/api/src/modules/system/log/services/login-log.service.ts b/apps/api/src/modules/system/log/services/login-log.service.ts index 2338cc5..e59dde8 100644 --- a/apps/api/src/modules/system/log/services/login-log.service.ts +++ b/apps/api/src/modules/system/log/services/login-log.service.ts @@ -1,20 +1,20 @@ -import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; +import { Injectable } from '@nestjs/common' +import { InjectRepository } from '@nestjs/typeorm' -import { Between, LessThan, Like, Repository } from 'typeorm'; +import { Between, LessThan, Like, Repository } from 'typeorm' -import UAParser from 'ua-parser-js'; +import UAParser from 'ua-parser-js' -import { paginateRaw } from '@/helper/paginate'; +import { paginateRaw } from '@/helper/paginate' -import { IpService } from '@/shared/ip/ip.service'; +import { IpService } from '@/shared/ip/ip.service' -import { LoginLogQueryDto } from '../dto/log.dto'; -import { LoginLogEntity } from '../entities/login-log.entity'; -import { LoginLogInfo } from '../models/log.model'; +import { LoginLogQueryDto } from '../dto/log.dto' +import { LoginLogEntity } from '../entities/login-log.entity' +import { LoginLogInfo } from '../models/log.model' async function parseLoginLog(e: any, parser: UAParser): Promise { - const uaResult = parser.setUA(e.login_log_ua).getResult(); + const uaResult = parser.setUA(e.login_log_ua).getResult() return { id: e.login_log_id, @@ -24,7 +24,7 @@ async function parseLoginLog(e: any, parser: UAParser): Promise { browser: `${`${uaResult.browser.name ?? ''} `}${uaResult.browser.version}`, time: e.login_log_created_at, username: e.user_username, - }; + } } @Injectable() @@ -38,16 +38,16 @@ export class LoginLogService { async create(uid: number, ip: string, ua: string): Promise { try { - const address = await this.ipService.getAddress(ip); + const address = await this.ipService.getAddress(ip) await this.loginLogRepository.save({ ip, ua, address, user: { id: uid }, - }); + }) } catch (e) { - console.error(e); + console.error(e) } } @@ -72,29 +72,29 @@ export class LoginLogService { }, }), }) - .orderBy('login_log.created_at', 'DESC'); + .orderBy('login_log.created_at', 'DESC') const { items, ...rest } = await paginateRaw(queryBuilder, { page, pageSize, - }); + }) - const parser = new UAParser(); + const parser = new UAParser() const loginLogInfos = await Promise.all( items.map((item) => parseLoginLog(item, parser)), - ); + ) return { items: loginLogInfos, ...rest, - }; + } } async clearLog(): Promise { - await this.loginLogRepository.clear(); + await this.loginLogRepository.clear() } async clearLogBeforeTime(time: Date): Promise { - await this.loginLogRepository.delete({ createdAt: LessThan(time) }); + await this.loginLogRepository.delete({ createdAt: LessThan(time) }) } } diff --git a/apps/api/src/modules/system/log/services/task-log.service.ts b/apps/api/src/modules/system/log/services/task-log.service.ts index 38733ef..bcccea3 100644 --- a/apps/api/src/modules/system/log/services/task-log.service.ts +++ b/apps/api/src/modules/system/log/services/task-log.service.ts @@ -1,12 +1,12 @@ -import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; +import { Injectable } from '@nestjs/common' +import { InjectRepository } from '@nestjs/typeorm' -import { LessThan, Repository } from 'typeorm'; +import { LessThan, Repository } from 'typeorm' -import { paginate } from '@/helper/paginate'; +import { paginate } from '@/helper/paginate' -import { TaskLogQueryDto } from '../dto/log.dto'; -import { TaskLogEntity } from '../entities/task-log.entity'; +import { TaskLogQueryDto } from '../dto/log.dto' +import { TaskLogEntity } from '../entities/task-log.entity' @Injectable() export class TaskLogService { @@ -26,27 +26,27 @@ export class TaskLogService { detail: err, time, task: { id: tid }, - }); - return result.id; + }) + return result.id } async list({ page, pageSize }: TaskLogQueryDto) { const queryBuilder = await this.taskLogRepository .createQueryBuilder('task_log') .leftJoinAndSelect('task_log.task', 'task') - .orderBy('task_log.id', 'DESC'); + .orderBy('task_log.id', 'DESC') return paginate(queryBuilder, { page, pageSize, - }); + }) } async clearLog(): Promise { - await this.taskLogRepository.clear(); + await this.taskLogRepository.clear() } async clearLogBeforeTime(time: Date): Promise { - await this.taskLogRepository.delete({ createdAt: LessThan(time) }); + await this.taskLogRepository.delete({ createdAt: LessThan(time) }) } } diff --git a/apps/api/src/modules/system/menu/menu.controller.ts b/apps/api/src/modules/system/menu/menu.controller.ts index 67c9504..8a249e3 100644 --- a/apps/api/src/modules/system/menu/menu.controller.ts +++ b/apps/api/src/modules/system/menu/menu.controller.ts @@ -7,18 +7,18 @@ import { Post, Put, Query, -} from '@nestjs/common'; -import { ApiOperation, ApiTags } from '@nestjs/swagger'; -import { flattenDeep } from 'lodash'; +} from '@nestjs/common' +import { ApiOperation, ApiTags } from '@nestjs/swagger' +import { flattenDeep } from 'lodash' -import { ApiResult } from '@/common/decorators/api-result.decorator'; -import { IdParam } from '@/common/decorators/id-param.decorator'; -import { ApiSecurityAuth } from '@/common/decorators/swagger.decorator'; -import { Permission } from '@/modules/auth/decorators/permission.decorator'; -import { MenuEntity } from '@/modules/system/menu/menu.entity'; +import { ApiResult } from '@/common/decorators/api-result.decorator' +import { IdParam } from '@/common/decorators/id-param.decorator' +import { ApiSecurityAuth } from '@/common/decorators/swagger.decorator' +import { Permission } from '@/modules/auth/decorators/permission.decorator' +import { MenuEntity } from '@/modules/system/menu/menu.entity' -import { MenuDto, MenuQueryDto } from './menu.dto'; -import { MenuService } from './menu.service'; +import { MenuDto, MenuQueryDto } from './menu.dto' +import { MenuService } from './menu.service' export const Permissions = { LIST: 'system:menu:list', @@ -26,7 +26,7 @@ export const Permissions = { READ: 'system:menu:read', UPDATE: 'system:menu:update', DELETE: 'system:menu:delete', -} as const; +} as const @ApiTags('System - 菜单权限模块') @ApiSecurityAuth() @@ -39,14 +39,14 @@ export class MenuController { @ApiResult({ type: [MenuEntity] }) @Permission(Permissions.LIST) async list(@Query() dto: MenuQueryDto) { - return this.menuService.list(dto); + return this.menuService.list(dto) } @Get(':id') @ApiOperation({ summary: '获取菜单或权限信息' }) @Permission(Permissions.READ) async info(@IdParam() id: number) { - return this.menuService.getMenuItemAndParentInfo(id); + return this.menuService.getMenuItemAndParentInfo(id) } @Post() @@ -54,18 +54,18 @@ export class MenuController { @Permission(Permissions.CREATE) async create(@Body() dto: MenuDto): Promise { // check - await this.menuService.check(dto); + await this.menuService.check(dto) if (!dto.parent) { - dto.parent = null; + dto.parent = null } if (dto.type === 0) { - dto.component = 'LAYOUT'; + dto.component = 'LAYOUT' } - await this.menuService.create(dto); + await this.menuService.create(dto) if (dto.type === 2) { // 如果是权限发生更改,则刷新所有在线用户的权限 - await this.menuService.refreshOnlineUserPerms(); + await this.menuService.refreshOnlineUserPerms() } } @@ -77,15 +77,15 @@ export class MenuController { @Body() dto: Partial, ): Promise { // check - await this.menuService.check(dto); + await this.menuService.check(dto) if (dto.parent === -1 || !dto.parent) { - dto.parent = null; + dto.parent = null } - await this.menuService.update(id, dto); + await this.menuService.update(id, dto) if (dto.type === 2) { // 如果是权限发生更改,则刷新所有在线用户的权限 - await this.menuService.refreshOnlineUserPerms(); + await this.menuService.refreshOnlineUserPerms() } } @@ -94,13 +94,13 @@ export class MenuController { @Permission(Permissions.DELETE) async delete(@IdParam() id: number): Promise { if (await this.menuService.checkRoleByMenuId(id)) { - throw new BadRequestException('该菜单存在关联角色,无法删除'); + throw new BadRequestException('该菜单存在关联角色,无法删除') } // 如果有子目录,一并删除 - const childMenus = await this.menuService.findChildMenus(id); - await this.menuService.deleteMenuItem(flattenDeep([id, childMenus])); + const childMenus = await this.menuService.findChildMenus(id) + await this.menuService.deleteMenuItem(flattenDeep([id, childMenus])) // 刷新在线用户权限 - await this.menuService.refreshOnlineUserPerms(); + await this.menuService.refreshOnlineUserPerms() } } diff --git a/apps/api/src/modules/system/menu/menu.dto.ts b/apps/api/src/modules/system/menu/menu.dto.ts index 52cc297..9290757 100644 --- a/apps/api/src/modules/system/menu/menu.dto.ts +++ b/apps/api/src/modules/system/menu/menu.dto.ts @@ -1,4 +1,4 @@ -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty } from '@nestjs/swagger' import { IsIn, IsInt, @@ -7,92 +7,92 @@ import { Min, MinLength, ValidateIf, -} from 'class-validator'; +} from 'class-validator' export class MenuDto { @ApiProperty({ description: '菜单类型' }) @IsIn([0, 1, 2]) - type: number; + type: number @ApiProperty({ description: '父级菜单' }) @IsOptional() - parent: number; + parent: number @ApiProperty({ description: '菜单或权限名称' }) @IsString() @MinLength(2) - name: string; + name: string @ApiProperty({ description: '排序' }) @IsInt() @Min(0) - orderNo: number; + orderNo: number @ApiProperty({ description: '前端路由地址' }) // @Matches(/^[/]$/) @ValidateIf((o) => o.type !== 2) - path: string; + path: string @ApiProperty({ description: '是否外链', default: 1 }) @ValidateIf((o) => o.type !== 2) @IsIn([0, 1]) - external: number; + external: number @ApiProperty({ description: '菜单是否显示', default: 1 }) @ValidateIf((o) => o.type !== 2) @IsIn([0, 1]) - show: number; + show: number @ApiProperty({ description: '开启页面缓存', default: 1 }) @ValidateIf((o) => o.type === 1) @IsIn([0, 1]) - keepalive: number; + keepalive: number @ApiProperty({ description: '状态', default: 1 }) @IsIn([0, 1]) - status: number; + status: number @ApiProperty({ description: '菜单图标' }) @IsOptional() @ValidateIf((o) => o.type !== 2) @IsString() - icon?: string; + icon?: string @ApiProperty({ description: '对应权限' }) @ValidateIf((o) => o.type === 2) @IsString() @IsOptional() - permission: string; + permission: string @ApiProperty({ description: '菜单路由路径或外链' }) @ValidateIf((o) => o.type !== 2) @IsString() @IsOptional() - component?: string; + component?: string } export class MenuQueryDto { @ApiProperty({ description: '菜单名称' }) @IsString() @IsOptional() - name?: string; + name?: string @ApiProperty({ description: '路由' }) @IsString() @IsOptional() - path?: string; + path?: string @ApiProperty({ description: '权限标识' }) @IsString() @IsOptional() - permission?: string; + permission?: string @ApiProperty({ description: '组件' }) @IsString() @IsOptional() - component?: string; + component?: string @ApiProperty({ description: '状态' }) @IsOptional() - status?: number; + status?: number } diff --git a/apps/api/src/modules/system/menu/menu.entity.ts b/apps/api/src/modules/system/menu/menu.entity.ts index 0e5bbd4..23f8f0f 100644 --- a/apps/api/src/modules/system/menu/menu.entity.ts +++ b/apps/api/src/modules/system/menu/menu.entity.ts @@ -1,50 +1,50 @@ -import { PrimaryGeneratedColumn, Column, Entity, ManyToMany } from 'typeorm'; +import { PrimaryGeneratedColumn, Column, Entity, ManyToMany } from 'typeorm' -import { AbstractEntity } from '@/common/entity/abstract.entity'; +import { AbstractEntity } from '@/common/entity/abstract.entity' -import { RoleEntity } from '../role/role.entity'; +import { RoleEntity } from '../role/role.entity' @Entity({ name: 'sys_menu' }) export class MenuEntity extends AbstractEntity { @PrimaryGeneratedColumn() - id: number; + id: number @Column({ nullable: true }) - parent: number; + parent: number @Column() - name: string; + name: string @Column({ nullable: true }) - path: string; + path: string @Column({ nullable: true }) - permission: string; + permission: string @Column({ type: 'tinyint', default: 0 }) - type: number; + type: number @Column({ nullable: true, default: '' }) - icon: string; + icon: string @Column({ name: 'order_no', type: 'int', nullable: true, default: 0 }) - orderNo: number; + orderNo: number @Column({ name: 'component', nullable: true }) - component: string; + component: string @Column({ type: 'tinyint', default: 0 }) - external: number; + external: number @Column({ type: 'tinyint', default: 1 }) - keepalive: number; + keepalive: number @Column({ type: 'tinyint', default: 1 }) - show: number; + show: number @Column({ type: 'tinyint', default: 1 }) - status: number; + status: number @ManyToMany(() => RoleEntity, (role) => role.menus) - roles: RoleEntity[]; + roles: RoleEntity[] } diff --git a/apps/api/src/modules/system/menu/menu.module.ts b/apps/api/src/modules/system/menu/menu.module.ts index a5bdffd..4a4b04b 100644 --- a/apps/api/src/modules/system/menu/menu.module.ts +++ b/apps/api/src/modules/system/menu/menu.module.ts @@ -1,13 +1,13 @@ -import { Module, forwardRef } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; +import { Module, forwardRef } from '@nestjs/common' +import { TypeOrmModule } from '@nestjs/typeorm' -import { RoleModule } from '../role/role.module'; +import { RoleModule } from '../role/role.module' -import { MenuController } from './menu.controller'; -import { MenuEntity } from './menu.entity'; -import { MenuService } from './menu.service'; +import { MenuController } from './menu.controller' +import { MenuEntity } from './menu.entity' +import { MenuService } from './menu.service' -const providers = [MenuService]; +const providers = [MenuService] @Module({ imports: [ diff --git a/apps/api/src/modules/system/menu/menu.service.ts b/apps/api/src/modules/system/menu/menu.service.ts index ae5765e..dfbbe4e 100644 --- a/apps/api/src/modules/system/menu/menu.service.ts +++ b/apps/api/src/modules/system/menu/menu.service.ts @@ -1,21 +1,21 @@ -import { InjectRedis } from '@liaoliaots/nestjs-redis'; -import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import Redis from 'ioredis'; -import { concat, isEmpty, uniq } from 'lodash'; +import { InjectRedis } from '@liaoliaots/nestjs-redis' +import { Injectable } from '@nestjs/common' +import { InjectRepository } from '@nestjs/typeorm' +import Redis from 'ioredis' +import { concat, isEmpty, uniq } from 'lodash' -import { In, IsNull, Like, Not, Repository } from 'typeorm'; +import { In, IsNull, Like, Not, Repository } from 'typeorm' -import { BusinessException } from '@/common/exceptions/biz.exception'; -import { ErrorEnum } from '@/constants/error-code.constant'; -import { MenuEntity } from '@/modules/system/menu/menu.entity'; +import { BusinessException } from '@/common/exceptions/biz.exception' +import { ErrorEnum } from '@/constants/error-code.constant' +import { MenuEntity } from '@/modules/system/menu/menu.entity' -import { deleteEmptyChildren } from '@/utils'; -import { generatorMenu, generatorRouters } from '@/utils/permission'; +import { deleteEmptyChildren } from '@/utils' +import { generatorMenu, generatorRouters } from '@/utils/permission' -import { RoleService } from '../role/role.service'; +import { RoleService } from '../role/role.service' -import { MenuDto, MenuQueryDto } from './menu.dto'; +import { MenuDto, MenuQueryDto } from './menu.dto' @Injectable() export class MenuService { @@ -45,45 +45,45 @@ export class MenuService { ...(status && { status }), }, order: { orderNo: 'ASC' }, - }); - const menuList = generatorMenu(menus); + }) + const menuList = generatorMenu(menus) if (!isEmpty(menuList)) { - deleteEmptyChildren(menuList); - return menuList; + deleteEmptyChildren(menuList) + return menuList } // 如果生产树形结构为空,则返回原始菜单列表 - return menus; + return menus } async create(menu: MenuDto): Promise { - await this.menuRepository.save(menu); + await this.menuRepository.save(menu) } async update(id: number, menu: Partial): Promise { - await this.menuRepository.update(id, menu); + await this.menuRepository.update(id, menu) } /** * 根据角色获取所有菜单 */ async getMenus(uid: number): Promise { - const roleIds = await this.roleService.getRoleIdsByUser(uid); - let menus: MenuEntity[] = []; + const roleIds = await this.roleService.getRoleIdsByUser(uid) + let menus: MenuEntity[] = [] if (this.roleService.hasAdminRole(roleIds)) { - menus = await this.menuRepository.find({ order: { orderNo: 'ASC' } }); + menus = await this.menuRepository.find({ order: { orderNo: 'ASC' } }) } else { menus = await this.menuRepository .createQueryBuilder('menu') .innerJoinAndSelect('menu.roles', 'role') .andWhere('role.id IN (:...roleIds)', { roleIds }) .orderBy('menu.order_no', 'ASC') - .getMany(); + .getMany() } - const menuList = generatorRouters(menus); - return menuList; + const menuList = generatorRouters(menus) + return menuList } /** @@ -92,18 +92,18 @@ export class MenuService { async check(dto: Partial): Promise { if (dto.type === 2 && !dto.parent) { // 无法直接创建权限,必须有parent - throw new BusinessException(ErrorEnum.PERMISSION_REQUIRES_PARENT); + throw new BusinessException(ErrorEnum.PERMISSION_REQUIRES_PARENT) } if (dto.type === 1 && dto.parent) { - const parent = await this.getMenuItemInfo(dto.parent); + const parent = await this.getMenuItemInfo(dto.parent) if (isEmpty(parent)) { - throw new BusinessException(ErrorEnum.PARENT_MENU_NOT_FOUND); + throw new BusinessException(ErrorEnum.PARENT_MENU_NOT_FOUND) } if (parent && parent.type === 1) { // 当前新增为菜单但父节点也为菜单时为非法操作 throw new BusinessException( ErrorEnum.ILLEGAL_OPERATION_DIRECTORY_PARENT, - ); + ) } } } @@ -112,8 +112,8 @@ export class MenuService { * 查找当前菜单下的子菜单,目录以及菜单 */ async findChildMenus(mid: number): Promise { - const allMenus: any = []; - const menus = await this.menuRepository.findBy({ parent: mid }); + const allMenus: any = [] + const menus = await this.menuRepository.findBy({ parent: mid }) // if (_.isEmpty(menus)) { // return allMenus; // } @@ -121,12 +121,12 @@ export class MenuService { for (const menu of menus) { if (menu.type !== 2) { // 子目录下是菜单或目录,继续往下级查找 - const c = await this.findChildMenus(menu.id); - allMenus.push(c); + const c = await this.findChildMenus(menu.id) + allMenus.push(c) } - allMenus.push(menu.id); + allMenus.push(menu.id) } - return allMenus; + return allMenus } /** @@ -134,45 +134,45 @@ export class MenuService { * @param mid menu id */ async getMenuItemInfo(mid: number): Promise { - const menu = await this.menuRepository.findOneBy({ id: mid }); - return menu; + const menu = await this.menuRepository.findOneBy({ id: mid }) + return menu } /** * 获取某个菜单以及关联的父菜单的信息 */ async getMenuItemAndParentInfo(mid: number) { - const menu = await this.menuRepository.findOneBy({ id: mid }); - let parentMenu: MenuEntity | undefined; + const menu = await this.menuRepository.findOneBy({ id: mid }) + let parentMenu: MenuEntity | undefined if (menu && menu.parent) { - parentMenu = await this.menuRepository.findOneBy({ id: menu.parent }); + parentMenu = await this.menuRepository.findOneBy({ id: menu.parent }) } - return { menu, parentMenu }; + return { menu, parentMenu } } /** * 查找节点路由是否存在 */ async findRouterExist(path: string): Promise { - const menus = await this.menuRepository.findOneBy({ path }); - return !isEmpty(menus); + const menus = await this.menuRepository.findOneBy({ path }) + return !isEmpty(menus) } /** * 获取当前用户的所有权限 */ async getPermissions(uid: number): Promise { - const roleIds = await this.roleService.getRoleIdsByUser(uid); - let permission: any[] = []; - let result: any = null; + const roleIds = await this.roleService.getRoleIdsByUser(uid) + let permission: any[] = [] + let result: any = null if (this.roleService.hasAdminRole(roleIds)) { result = await this.menuRepository.findBy({ permission: Not(IsNull()), type: In([1, 2]), - }); + }) } else { if (isEmpty(roleIds)) { - return permission; + return permission } result = await this.menuRepository .createQueryBuilder('menu') @@ -180,35 +180,35 @@ export class MenuService { .andWhere('role.id IN (:...roleIds)', { roleIds }) .andWhere('menu.type IN (1,2)') .andWhere('menu.permission IS NOT NULL') - .getMany(); + .getMany() } if (!isEmpty(result)) { result.forEach((e) => { if (e.permission) { - permission = concat(permission, e.permission.split(',')); + permission = concat(permission, e.permission.split(',')) } - }); - permission = uniq(permission); + }) + permission = uniq(permission) } - return permission; + return permission } /** * 删除多项菜单 */ async deleteMenuItem(mids: number[]): Promise { - await this.menuRepository.delete(mids); + await this.menuRepository.delete(mids) } /** * 刷新指定用户ID的权限 */ async refreshPerms(uid: number): Promise { - const perms = await this.getPermissions(uid); - const online = await this.redis.get(`admin:token:${uid}`); + const perms = await this.getPermissions(uid) + const online = await this.redis.get(`admin:token:${uid}`) if (online) { // 判断是否在线 - await this.redis.set(`admin:perms:${uid}`, JSON.stringify(perms)); + await this.redis.set(`admin:perms:${uid}`, JSON.stringify(perms)) } } @@ -216,15 +216,15 @@ export class MenuService { * 刷新所有在线用户的权限 */ async refreshOnlineUserPerms(): Promise { - const onlineUserIds: string[] = await this.redis.keys('admin:token:*'); + const onlineUserIds: string[] = await this.redis.keys('admin:token:*') if (onlineUserIds && onlineUserIds.length > 0) { onlineUserIds .map((i) => i.split('admin:token:')[1]) .filter((i) => i) .forEach(async (uid) => { - const perms = await this.getPermissions(parseInt(uid)); - await this.redis.set(`admin:perms:${uid}`, JSON.stringify(perms)); - }); + const perms = await this.getPermissions(parseInt(uid)) + await this.redis.set(`admin:perms:${uid}`, JSON.stringify(perms)) + }) } } @@ -238,6 +238,6 @@ export class MenuService { id, }, }, - })); + })) } } diff --git a/apps/api/src/modules/system/online/online.controller.ts b/apps/api/src/modules/system/online/online.controller.ts index a248a29..a97448f 100644 --- a/apps/api/src/modules/system/online/online.controller.ts +++ b/apps/api/src/modules/system/online/online.controller.ts @@ -1,18 +1,18 @@ -import { Body, Controller, Get, Post } from '@nestjs/common'; -import { ApiExtraModels, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { Body, Controller, Get, Post } from '@nestjs/common' +import { ApiExtraModels, ApiOperation, ApiTags } from '@nestjs/swagger' -import { ApiResult } from '@/common/decorators/api-result.decorator'; -import { ApiSecurityAuth } from '@/common/decorators/swagger.decorator'; -import { BusinessException } from '@/common/exceptions/biz.exception'; -import { ErrorEnum } from '@/constants/error-code.constant'; +import { ApiResult } from '@/common/decorators/api-result.decorator' +import { ApiSecurityAuth } from '@/common/decorators/swagger.decorator' +import { BusinessException } from '@/common/exceptions/biz.exception' +import { ErrorEnum } from '@/constants/error-code.constant' -import { AuthUser } from '@/modules/auth/decorators/auth-user.decorator'; +import { AuthUser } from '@/modules/auth/decorators/auth-user.decorator' -import { Permission } from '@/modules/auth/decorators/permission.decorator'; +import { Permission } from '@/modules/auth/decorators/permission.decorator' -import { KickDto } from './online.dto'; -import { OnlineUserInfo } from './online.model'; -import { OnlineService } from './online.service'; +import { KickDto } from './online.dto' +import { OnlineUserInfo } from './online.model' +import { OnlineService } from './online.service' @ApiTags('System - 在线用户模块') @ApiSecurityAuth() @@ -26,7 +26,7 @@ export class OnlineController { @ApiResult({ type: [OnlineUserInfo] }) @Permission('system:online:list') async list(@AuthUser() user: IAuthUser): Promise { - return this.onlineService.listOnlineUser(user.uid); + return this.onlineService.listOnlineUser(user.uid) } @Post('kick') @@ -34,8 +34,8 @@ export class OnlineController { @Permission('system:online:kick') async kick(@Body() dto: KickDto, @AuthUser() user: IAuthUser): Promise { if (dto.id === user.uid) { - throw new BusinessException(ErrorEnum.NOT_ALLOWED_TO_LOGOUT_USER); + throw new BusinessException(ErrorEnum.NOT_ALLOWED_TO_LOGOUT_USER) } - await this.onlineService.kickUser(dto.id, user.uid); + await this.onlineService.kickUser(dto.id, user.uid) } } diff --git a/apps/api/src/modules/system/online/online.dto.ts b/apps/api/src/modules/system/online/online.dto.ts index 736a2c8..fbe8f8b 100644 --- a/apps/api/src/modules/system/online/online.dto.ts +++ b/apps/api/src/modules/system/online/online.dto.ts @@ -1,8 +1,8 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsInt } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger' +import { IsInt } from 'class-validator' export class KickDto { @ApiProperty({ description: '需要下线的角色ID' }) @IsInt() - id: number; + id: number } diff --git a/apps/api/src/modules/system/online/online.model.ts b/apps/api/src/modules/system/online/online.model.ts index 79a0dcd..fec4eb8 100644 --- a/apps/api/src/modules/system/online/online.model.ts +++ b/apps/api/src/modules/system/online/online.model.ts @@ -1,30 +1,30 @@ -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty } from '@nestjs/swagger' export class OnlineUserInfo { @ApiProperty({ description: '最近的一条登录日志ID' }) - id: number; + id: number @ApiProperty({ description: '登录IP' }) - ip: string; + ip: string @ApiProperty({ description: '登录地点' }) - address: string; + address: string @ApiProperty({ description: '用户名' }) - username: string; + username: string @ApiProperty({ description: '是否当前' }) - isCurrent: boolean; + isCurrent: boolean @ApiProperty({ description: '登陆时间' }) - time: string; + time: string @ApiProperty({ description: '系统' }) - os: string; + os: string @ApiProperty({ description: '浏览器' }) - browser: string; + browser: string @ApiProperty({ description: '是否禁用' }) - disable: boolean; + disable: boolean } diff --git a/apps/api/src/modules/system/online/online.module.ts b/apps/api/src/modules/system/online/online.module.ts index e11fe6e..f177801 100644 --- a/apps/api/src/modules/system/online/online.module.ts +++ b/apps/api/src/modules/system/online/online.module.ts @@ -1,16 +1,16 @@ -import { Module, forwardRef } from '@nestjs/common'; +import { Module, forwardRef } from '@nestjs/common' -import { AuthModule } from '@/modules/auth/auth.module'; -import { SocketModule } from '@/modules/socket/socket.module'; +import { AuthModule } from '@/modules/auth/auth.module' +import { SocketModule } from '@/modules/socket/socket.module' -import { UserModule } from '../../user/user.module'; -import { RoleModule } from '../role/role.module'; -import { SystemModule } from '../system.module'; +import { UserModule } from '../../user/user.module' +import { RoleModule } from '../role/role.module' +import { SystemModule } from '../system.module' -import { OnlineController } from './online.controller'; -import { OnlineService } from './online.service'; +import { OnlineController } from './online.controller' +import { OnlineService } from './online.service' -const providers = [OnlineService]; +const providers = [OnlineService] @Module({ imports: [ diff --git a/apps/api/src/modules/system/online/online.service.ts b/apps/api/src/modules/system/online/online.service.ts index 928c0a9..c0d0e6f 100644 --- a/apps/api/src/modules/system/online/online.service.ts +++ b/apps/api/src/modules/system/online/online.service.ts @@ -1,20 +1,20 @@ -import { Injectable } from '@nestjs/common'; -import { JwtService } from '@nestjs/jwt'; -import { InjectEntityManager } from '@nestjs/typeorm'; +import { Injectable } from '@nestjs/common' +import { JwtService } from '@nestjs/jwt' +import { InjectEntityManager } from '@nestjs/typeorm' -import { EntityManager } from 'typeorm'; +import { EntityManager } from 'typeorm' -import { UAParser } from 'ua-parser-js'; +import { UAParser } from 'ua-parser-js' -import { BusinessException } from '@/common/exceptions/biz.exception'; -import { ErrorEnum } from '@/constants/error-code.constant'; -import { AdminWSGateway } from '@/modules/socket/admin-ws.gateway'; -import { AdminWSService } from '@/modules/socket/admin-ws.service'; -import { EVENT_KICK } from '@/modules/socket/socket.event'; +import { BusinessException } from '@/common/exceptions/biz.exception' +import { ErrorEnum } from '@/constants/error-code.constant' +import { AdminWSGateway } from '@/modules/socket/admin-ws.gateway' +import { AdminWSService } from '@/modules/socket/admin-ws.service' +import { EVENT_KICK } from '@/modules/socket/socket.event' -import { UserService } from '../../user/user.service'; +import { UserService } from '../../user/user.service' -import { OnlineUserInfo } from './online.model'; +import { OnlineUserInfo } from './online.model' @Injectable() export class OnlineService { @@ -30,37 +30,37 @@ export class OnlineService { * 罗列在线用户列表 */ async listOnlineUser(currentUid: number): Promise { - const onlineSockets = await this.adminWSService.getOnlineSockets(); + const onlineSockets = await this.adminWSService.getOnlineSockets() if (!onlineSockets || onlineSockets.length <= 0) { - return []; + return [] } const onlineIds = onlineSockets.map((socket) => { - const token = socket.handshake.query?.token as string; - return this.jwtService.verify(token).uid; - }); - return this.findLastLoginInfoList(onlineIds, currentUid); + const token = socket.handshake.query?.token as string + return this.jwtService.verify(token).uid + }) + return this.findLastLoginInfoList(onlineIds, currentUid) } /** * 下线当前用户 */ async kickUser(uid: number, currentUid: number): Promise { - const rootUserId = await this.userService.findRootUserId(); - const currentUserInfo = await this.userService.getAccountInfo(currentUid); + const rootUserId = await this.userService.findRootUserId() + const currentUserInfo = await this.userService.getAccountInfo(currentUid) if (uid === rootUserId) { - throw new BusinessException(ErrorEnum.NOT_ALLOWED_TO_LOGOUT_USER); + throw new BusinessException(ErrorEnum.NOT_ALLOWED_TO_LOGOUT_USER) } // reset redis keys - await this.userService.forbidden(uid); + await this.userService.forbidden(uid) // socket emit - const socket = await this.adminWSService.findSocketIdByUid(uid); + const socket = await this.adminWSService.findSocketIdByUid(uid) if (socket) { // socket emit event this.adminWsGateWay.socketServer .to(socket.id) - .emit(EVENT_KICK, { operater: currentUserInfo.username }); + .emit(EVENT_KICK, { operater: currentUserInfo.username }) // close socket - socket.disconnect(); + socket.disconnect() } } @@ -71,7 +71,7 @@ export class OnlineService { ids: number[], currentUid: number, ): Promise { - const rootUserId = await this.userService.findRootUserId(); + const rootUserId = await this.userService.findRootUserId() const result = await this.entityManager.query( ` SELECT sys_login_log.created_at, sys_login_log.ip, sys_login_log.address, sys_login_log.ua, sys_user.id, sys_user.username, sys_user.nick_name @@ -81,11 +81,11 @@ export class OnlineService { AND sys_user.id IN (?) `, [ids], - ); + ) if (result) { - const parser = new UAParser(); + const parser = new UAParser() return result.map((e) => { - const u = parser.setUA(e.ua).getResult(); + const u = parser.setUA(e.ua).getResult() return { id: e.id, ip: e.ip, @@ -96,9 +96,9 @@ export class OnlineService { os: `${u.os.name} ${u.os.version}`, browser: `${u.browser.name} ${u.browser.version}`, disable: currentUid === e.id || e.id === rootUserId, - }; - }); + } + }) } - return []; + return [] } } diff --git a/apps/api/src/modules/system/role/role.controller.ts b/apps/api/src/modules/system/role/role.controller.ts index 36e048f..798ef23 100644 --- a/apps/api/src/modules/system/role/role.controller.ts +++ b/apps/api/src/modules/system/role/role.controller.ts @@ -7,20 +7,20 @@ import { Post, Put, Query, -} from '@nestjs/common'; -import { ApiOperation, ApiTags } from '@nestjs/swagger'; +} from '@nestjs/common' +import { ApiOperation, ApiTags } from '@nestjs/swagger' -import { ApiResult } from '@/common/decorators/api-result.decorator'; -import { IdParam } from '@/common/decorators/id-param.decorator'; -import { ApiSecurityAuth } from '@/common/decorators/swagger.decorator'; -import { PageOptionsDto } from '@/common/dto/page-options.dto'; -import { Permission } from '@/modules/auth/decorators/permission.decorator'; -import { RoleEntity } from '@/modules/system/role/role.entity'; +import { ApiResult } from '@/common/decorators/api-result.decorator' +import { IdParam } from '@/common/decorators/id-param.decorator' +import { ApiSecurityAuth } from '@/common/decorators/swagger.decorator' +import { PageOptionsDto } from '@/common/dto/page-options.dto' +import { Permission } from '@/modules/auth/decorators/permission.decorator' +import { RoleEntity } from '@/modules/system/role/role.entity' -import { MenuService } from '../menu/menu.service'; +import { MenuService } from '../menu/menu.service' -import { RoleDto } from './role.dto'; -import { RoleService } from './role.service'; +import { RoleDto } from './role.dto' +import { RoleService } from './role.service' export const Permissions = { LIST: 'system:role:list', @@ -28,7 +28,7 @@ export const Permissions = { READ: 'system:role:read', UPDATE: 'system:role:update', DELETE: 'system:role:delete', -} as const; +} as const @ApiTags('System - 角色模块') @ApiSecurityAuth() @@ -44,7 +44,7 @@ export class RoleController { @ApiResult({ type: [RoleEntity] }) @Permission(Permissions.LIST) async list(@Query() dto: PageOptionsDto) { - return this.roleService.findAll(dto); + return this.roleService.findAll(dto) } @Get(':id') @@ -52,14 +52,14 @@ export class RoleController { @ApiResult({ type: RoleEntity }) @Permission(Permissions.READ) async info(@IdParam() id: number) { - return this.roleService.info(id); + return this.roleService.info(id) } @Post() @ApiOperation({ summary: '新增角色' }) @Permission(Permissions.CREATE) async create(@Body() dto: RoleDto): Promise { - await this.roleService.create(dto); + await this.roleService.create(dto) } @Put(':id') @@ -69,8 +69,8 @@ export class RoleController { @IdParam() id: number, @Body() dto: Partial, ): Promise { - await this.roleService.update(id, dto); - await this.menuService.refreshOnlineUserPerms(); + await this.roleService.update(id, dto) + await this.menuService.refreshOnlineUserPerms() } @Delete(':id') @@ -78,10 +78,10 @@ export class RoleController { @Permission(Permissions.DELETE) async delete(@IdParam() id: number): Promise { if (await this.roleService.checkUserByRoleId(id)) { - throw new BadRequestException('该角色存在关联用户,无法删除'); + throw new BadRequestException('该角色存在关联用户,无法删除') } - await this.roleService.delete(id); - await this.menuService.refreshOnlineUserPerms(); + await this.roleService.delete(id) + await this.menuService.refreshOnlineUserPerms() } } diff --git a/apps/api/src/modules/system/role/role.dto.ts b/apps/api/src/modules/system/role/role.dto.ts index fb47ead..48e1856 100644 --- a/apps/api/src/modules/system/role/role.dto.ts +++ b/apps/api/src/modules/system/role/role.dto.ts @@ -1,4 +1,4 @@ -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty } from '@nestjs/swagger' import { IsArray, IsIn, @@ -6,31 +6,31 @@ import { IsString, Matches, MinLength, -} from 'class-validator'; +} from 'class-validator' export class RoleDto { @ApiProperty({ description: '角色名称' }) @IsString() @MinLength(2, { message: '角色名称长度不能小于2' }) - name: string; + name: string @ApiProperty({ description: '角色值' }) @IsString() @Matches(/^[a-z0-9A-Z]+$/, { message: '角色值只能包含字母和数字' }) @MinLength(2, { message: '角色值长度不能小于2' }) - value: string; + value: string @ApiProperty({ description: '角色备注' }) @IsString() @IsOptional() - remark?: string; + remark?: string @ApiProperty({ description: '状态' }) @IsIn([0, 1]) - status: number; + status: number @ApiProperty({ description: '关联菜单、权限编号' }) @IsOptional() @IsArray() - menuIds?: number[]; + menuIds?: number[] } diff --git a/apps/api/src/modules/system/role/role.entity.ts b/apps/api/src/modules/system/role/role.entity.ts index a5941e8..ec18a43 100644 --- a/apps/api/src/modules/system/role/role.entity.ts +++ b/apps/api/src/modules/system/role/role.entity.ts @@ -1,43 +1,43 @@ -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty } from '@nestjs/swagger' import { PrimaryGeneratedColumn, Column, Entity, JoinTable, ManyToMany, -} from 'typeorm'; +} from 'typeorm' -import { AbstractEntity } from '@/common/entity/abstract.entity'; +import { AbstractEntity } from '@/common/entity/abstract.entity' -import { UserEntity } from '../../user/entities/user.entity'; -import { MenuEntity } from '../menu/menu.entity'; +import { UserEntity } from '../../user/entities/user.entity' +import { MenuEntity } from '../menu/menu.entity' @Entity({ name: 'sys_role' }) export class RoleEntity extends AbstractEntity { @PrimaryGeneratedColumn() @ApiProperty() - id: number; + id: number @Column({ length: 50, unique: true }) @ApiProperty({ description: '角色名' }) - name: string; + name: string @Column({ unique: true }) @ApiProperty({ description: '角色标识' }) - value: string; + value: string @Column({ nullable: true }) @ApiProperty({ description: '角色描述' }) - remark: string; + remark: string @Column({ type: 'tinyint', nullable: true, default: 1 }) @ApiProperty({ description: '状态:1启用,0禁用' }) - status: number; + status: number @ManyToMany(() => UserEntity, (user) => user.roles) - users: UserEntity[]; + users: UserEntity[] @ManyToMany(() => MenuEntity, (menu) => menu.roles, {}) @JoinTable() - menus: MenuEntity[]; + menus: MenuEntity[] } diff --git a/apps/api/src/modules/system/role/role.model.ts b/apps/api/src/modules/system/role/role.model.ts index eaf0a37..22dbf89 100644 --- a/apps/api/src/modules/system/role/role.model.ts +++ b/apps/api/src/modules/system/role/role.model.ts @@ -1,5 +1,5 @@ -import { RoleEntity } from './role.entity'; +import { RoleEntity } from './role.entity' export class RoleInfo extends RoleEntity { - menuIds: number[]; + menuIds: number[] } diff --git a/apps/api/src/modules/system/role/role.module.ts b/apps/api/src/modules/system/role/role.module.ts index bc3681c..5b5db29 100644 --- a/apps/api/src/modules/system/role/role.module.ts +++ b/apps/api/src/modules/system/role/role.module.ts @@ -1,13 +1,13 @@ -import { Module, forwardRef } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; +import { Module, forwardRef } from '@nestjs/common' +import { TypeOrmModule } from '@nestjs/typeorm' -import { MenuModule } from '../menu/menu.module'; +import { MenuModule } from '../menu/menu.module' -import { RoleController } from './role.controller'; -import { RoleEntity } from './role.entity'; -import { RoleService } from './role.service'; +import { RoleController } from './role.controller' +import { RoleEntity } from './role.entity' +import { RoleService } from './role.service' -const providers = [RoleService]; +const providers = [RoleService] @Module({ imports: [ diff --git a/apps/api/src/modules/system/role/role.service.ts b/apps/api/src/modules/system/role/role.service.ts index 0f197cb..fb2d0b6 100644 --- a/apps/api/src/modules/system/role/role.service.ts +++ b/apps/api/src/modules/system/role/role.service.ts @@ -1,18 +1,18 @@ -import { Injectable } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm'; -import { isEmpty } from 'lodash'; -import { EntityManager, In, Repository } from 'typeorm'; - -import { PageOptionsDto } from '@/common/dto/page-options.dto'; -import { IAppConfig } from '@/config'; -import { paginate } from '@/helper/paginate'; -import { Pagination } from '@/helper/paginate/pagination'; -import { MenuEntity } from '@/modules/system/menu/menu.entity'; -import { RoleEntity } from '@/modules/system/role/role.entity'; - -import { RoleDto } from './role.dto'; -import { RoleInfo } from './role.model'; +import { Injectable } from '@nestjs/common' +import { ConfigService } from '@nestjs/config' +import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm' +import { isEmpty } from 'lodash' +import { EntityManager, In, Repository } from 'typeorm' + +import { PageOptionsDto } from '@/common/dto/page-options.dto' +import { IAppConfig } from '@/config' +import { paginate } from '@/helper/paginate' +import { Pagination } from '@/helper/paginate/pagination' +import { MenuEntity } from '@/modules/system/menu/menu.entity' +import { RoleEntity } from '@/modules/system/role/role.entity' + +import { RoleDto } from './role.dto' +import { RoleInfo } from './role.model' @Injectable() export class RoleService { @@ -32,7 +32,7 @@ export class RoleService { page, pageSize, }: PageOptionsDto): Promise> { - return paginate(this.roleRepository, { page, pageSize }); + return paginate(this.roleRepository, { page, pageSize }) } /** @@ -44,7 +44,7 @@ export class RoleService { .where({ id, }) - .getOne(); + .getOne() // if (id === this.configService.get('app').adminRoleId) { // const menus = await this.menuRepository.find({ select: ['id'] }); @@ -54,17 +54,17 @@ export class RoleService { const menus = await this.menuRepository.find({ where: { roles: { id } }, select: ['id'], - }); + }) - return { ...info, menuIds: menus.map((m) => m.id) }; + return { ...info, menuIds: menus.map((m) => m.id) } } async delete(id: number): Promise { if (id === this.configService.get('app').adminRoleId) { - throw new Error('不能删除超级管理员'); + throw new Error('不能删除超级管理员') } - await this.roleRepository.delete(id); + await this.roleRepository.delete(id) } /** @@ -76,28 +76,28 @@ export class RoleService { menus: menuIds ? await this.menuRepository.findBy({ id: In(menuIds) }) : [], - }); + }) - return { roleId: role.id }; + return { roleId: role.id } } /** * 更新角色信息 */ async update(id, { menuIds, ...data }: Partial): Promise { - await this.roleRepository.update(id, data); + await this.roleRepository.update(id, data) if (!isEmpty(menuIds)) { // using transaction await this.entityManager.transaction(async (manager) => { const menus = await this.menuRepository.find({ where: { id: In(menuIds) }, - }); + }) - const role = await this.roleRepository.findOne({ where: { id } }); - role.menus = menus; - await manager.save(role); - }); + const role = await this.roleRepository.findOne({ where: { id } }) + role.menus = menus + await manager.save(role) + }) } } @@ -109,12 +109,12 @@ export class RoleService { where: { users: { id }, }, - }); + }) if (!isEmpty(roles)) { - return roles.map((r) => r.id); + return roles.map((r) => r.id) } - return []; + return [] } async getRoleValues(ids: number[]): Promise { @@ -122,7 +122,7 @@ export class RoleService { await this.roleRepository.findBy({ id: In(ids), }) - ).map((r) => r.value); + ).map((r) => r.value) } async isAdminRoleByUser(uid: number): Promise { @@ -130,20 +130,20 @@ export class RoleService { where: { users: { id: uid }, }, - }); + }) if (!isEmpty(roles)) { return roles.some( (r) => r.id === this.configService.get('app').adminRoleId, - ); + ) } - return false; + return false } hasAdminRole(rids: number[]): boolean { return rids.some( (r) => r === this.configService.get('app').adminRoleId, - ); + ) } /** @@ -156,6 +156,6 @@ export class RoleService { id, }, }, - })); + })) } } diff --git a/apps/api/src/modules/system/serve/serve.controller.ts b/apps/api/src/modules/system/serve/serve.controller.ts index 9c92f8c..ee39b08 100644 --- a/apps/api/src/modules/system/serve/serve.controller.ts +++ b/apps/api/src/modules/system/serve/serve.controller.ts @@ -1,15 +1,15 @@ -import { CacheInterceptor, CacheKey, CacheTTL } from '@nestjs/cache-manager'; -import { Controller, Get, UseInterceptors } from '@nestjs/common'; -import { ApiExtraModels, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { CacheInterceptor, CacheKey, CacheTTL } from '@nestjs/cache-manager' +import { Controller, Get, UseInterceptors } from '@nestjs/common' +import { ApiExtraModels, ApiOperation, ApiTags } from '@nestjs/swagger' -import { ApiResult } from '@/common/decorators/api-result.decorator'; +import { ApiResult } from '@/common/decorators/api-result.decorator' -import { ApiSecurityAuth } from '@/common/decorators/swagger.decorator'; +import { ApiSecurityAuth } from '@/common/decorators/swagger.decorator' -import { AllowAnon } from '@/modules/auth/decorators/allow-anon.decorator'; +import { AllowAnon } from '@/modules/auth/decorators/allow-anon.decorator' -import { ServeStatInfo } from './serve.model'; -import { ServeService } from './serve.service'; +import { ServeStatInfo } from './serve.model' +import { ServeService } from './serve.service' @ApiTags('System - 服务监控') @ApiSecurityAuth() @@ -26,6 +26,6 @@ export class ServeController { @ApiResult({ type: ServeStatInfo }) @AllowAnon() async stat(): Promise { - return this.serveService.getServeStat(); + return this.serveService.getServeStat() } } diff --git a/apps/api/src/modules/system/serve/serve.model.ts b/apps/api/src/modules/system/serve/serve.model.ts index d6fcf78..14df92a 100644 --- a/apps/api/src/modules/system/serve/serve.model.ts +++ b/apps/api/src/modules/system/serve/serve.model.ts @@ -1,71 +1,71 @@ -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty } from '@nestjs/swagger' export class Runtime { @ApiProperty({ description: '系统' }) - os?: string; + os?: string @ApiProperty({ description: '服务器架构' }) - arch?: string; + arch?: string @ApiProperty({ description: 'Node版本' }) - nodeVersion?: string; + nodeVersion?: string @ApiProperty({ description: 'Npm版本' }) - npmVersion?: string; + npmVersion?: string } export class CoreLoad { @ApiProperty({ description: '当前CPU资源消耗' }) - rawLoad?: number; + rawLoad?: number @ApiProperty({ description: '当前空闲CPU资源' }) - rawLoadIdle?: number; + rawLoadIdle?: number } // Intel(R) Xeon(R) Platinum 8163 CPU @ 2.50GHz export class Cpu { @ApiProperty({ description: '制造商 e.g. Intel(R)' }) - manufacturer?: string; + manufacturer?: string @ApiProperty({ description: '品牌 e.g. Core(TM)2 Duo' }) - brand?: string; + brand?: string @ApiProperty({ description: '物理核心数' }) - physicalCores?: number; + physicalCores?: number @ApiProperty({ description: '型号' }) - model?: string; + model?: string @ApiProperty({ description: '速度 in GHz e.g. 3.4' }) - speed?: number; + speed?: number @ApiProperty({ description: 'CPU资源消耗 原始滴答' }) - rawCurrentLoad?: number; + rawCurrentLoad?: number @ApiProperty({ description: '空闲CPU资源 原始滴答' }) - rawCurrentLoadIdle?: number; + rawCurrentLoadIdle?: number @ApiProperty({ description: 'cpu资源消耗', type: [CoreLoad] }) - coresLoad?: CoreLoad[]; + coresLoad?: CoreLoad[] } export class Disk { @ApiProperty({ description: '磁盘空间大小 (bytes)' }) - size?: number; + size?: number @ApiProperty({ description: '已使用磁盘空间 (bytes)' }) - used?: number; + used?: number @ApiProperty({ description: '可用磁盘空间 (bytes)' }) - available?: number; + available?: number } export class Memory { @ApiProperty({ description: 'total memory in bytes' }) - total?: number; + total?: number @ApiProperty({ description: '可用内存' }) - available?: number; + available?: number } /** @@ -73,14 +73,14 @@ export class Memory { */ export class ServeStatInfo { @ApiProperty({ description: '运行环境', type: Runtime }) - runtime?: Runtime; + runtime?: Runtime @ApiProperty({ description: 'CPU信息', type: Cpu }) - cpu?: Cpu; + cpu?: Cpu @ApiProperty({ description: '磁盘信息', type: Disk }) - disk?: Disk; + disk?: Disk @ApiProperty({ description: '内存信息', type: Memory }) - memory?: Memory; + memory?: Memory } diff --git a/apps/api/src/modules/system/serve/serve.module.ts b/apps/api/src/modules/system/serve/serve.module.ts index a2963cc..db44177 100644 --- a/apps/api/src/modules/system/serve/serve.module.ts +++ b/apps/api/src/modules/system/serve/serve.module.ts @@ -1,11 +1,11 @@ -import { Module, forwardRef } from '@nestjs/common'; +import { Module, forwardRef } from '@nestjs/common' -import { SystemModule } from '../system.module'; +import { SystemModule } from '../system.module' -import { ServeController } from './serve.controller'; -import { ServeService } from './serve.service'; +import { ServeController } from './serve.controller' +import { ServeService } from './serve.service' -const providers = [ServeService]; +const providers = [ServeService] @Module({ imports: [forwardRef(() => SystemModule)], diff --git a/apps/api/src/modules/system/serve/serve.service.ts b/apps/api/src/modules/system/serve/serve.service.ts index b6a8eab..2b92f3a 100644 --- a/apps/api/src/modules/system/serve/serve.service.ts +++ b/apps/api/src/modules/system/serve/serve.service.ts @@ -1,7 +1,7 @@ -import { Injectable } from '@nestjs/common'; -import * as si from 'systeminformation'; +import { Injectable } from '@nestjs/common' +import * as si from 'systeminformation' -import { Disk, ServeStatInfo } from './serve.model'; +import { Disk, ServeStatInfo } from './serve.model' @Injectable() export class ServeService { @@ -17,19 +17,19 @@ export class ServeService { si.currentLoad(), si.mem(), ]) - ).map((p: any) => p.value); + ).map((p: any) => p.value) // 计算总空间 - const diskListInfo = await si.fsSize(); - const diskinfo = new Disk(); - diskinfo.size = 0; - diskinfo.available = 0; - diskinfo.used = 0; + const diskListInfo = await si.fsSize() + const diskinfo = new Disk() + diskinfo.size = 0 + diskinfo.available = 0 + diskinfo.used = 0 diskListInfo.forEach((d) => { - diskinfo.size += d.size; - diskinfo.available += d.available; - diskinfo.used += d.used; - }); + diskinfo.size += d.size + diskinfo.available += d.available + diskinfo.used += d.used + }) return { runtime: { @@ -50,7 +50,7 @@ export class ServeService { return { rawLoad: e.rawLoad, rawLoadIdle: e.rawLoadIdle, - }; + } }), }, disk: diskinfo, @@ -58,6 +58,6 @@ export class ServeService { total: meminfo.total, available: meminfo.available, }, - }; + } } } diff --git a/apps/api/src/modules/system/system.module.ts b/apps/api/src/modules/system/system.module.ts index e573cdf..8660998 100644 --- a/apps/api/src/modules/system/system.module.ts +++ b/apps/api/src/modules/system/system.module.ts @@ -1,17 +1,17 @@ -import { Module } from '@nestjs/common'; +import { Module } from '@nestjs/common' -import { RouterModule } from '@nestjs/core'; +import { RouterModule } from '@nestjs/core' -import { UserModule } from '../user/user.module'; +import { UserModule } from '../user/user.module' -import { DeptModule } from './dept/dept.module'; -import { DictModule } from './dict/dict.module'; -import { LogModule } from './log/log.module'; -import { MenuModule } from './menu/menu.module'; -import { OnlineModule } from './online/online.module'; -import { RoleModule } from './role/role.module'; -import { ServeModule } from './serve/serve.module'; -import { TaskModule } from './task/task.module'; +import { DeptModule } from './dept/dept.module' +import { DictModule } from './dict/dict.module' +import { LogModule } from './log/log.module' +import { MenuModule } from './menu/menu.module' +import { OnlineModule } from './online/online.module' +import { RoleModule } from './role/role.module' +import { ServeModule } from './serve/serve.module' +import { TaskModule } from './task/task.module' const modules = [ UserModule, @@ -23,7 +23,7 @@ const modules = [ TaskModule, OnlineModule, ServeModule, -]; +] @Module({ imports: [ diff --git a/apps/api/src/modules/system/task/constant.ts b/apps/api/src/modules/system/task/constant.ts index e32bf7a..53f5d9b 100644 --- a/apps/api/src/modules/system/task/constant.ts +++ b/apps/api/src/modules/system/task/constant.ts @@ -8,5 +8,5 @@ export enum TaskType { Interval = 1, } -export const SYS_TASK_QUEUE_NAME = 'system:sys-task'; -export const SYS_TASK_QUEUE_PREFIX = 'system:sys:task'; +export const SYS_TASK_QUEUE_NAME = 'system:sys-task' +export const SYS_TASK_QUEUE_PREFIX = 'system:sys:task' diff --git a/apps/api/src/modules/system/task/task.controller.ts b/apps/api/src/modules/system/task/task.controller.ts index 3349cce..575bf97 100644 --- a/apps/api/src/modules/system/task/task.controller.ts +++ b/apps/api/src/modules/system/task/task.controller.ts @@ -1,23 +1,15 @@ -import { - Body, - Controller, - Delete, - Get, - Post, - Put, - Query, -} from '@nestjs/common'; -import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { Body, Controller, Delete, Get, Post, Put, Query } from '@nestjs/common' +import { ApiOperation, ApiTags } from '@nestjs/swagger' -import { ApiResult } from '@/common/decorators/api-result.decorator'; -import { IdParam } from '@/common/decorators/id-param.decorator'; -import { ApiSecurityAuth } from '@/common/decorators/swagger.decorator'; -import { Pagination } from '@/helper/paginate/pagination'; -import { Permission } from '@/modules/auth/decorators/permission.decorator'; -import { TaskEntity } from '@/modules/system/task/task.entity'; +import { ApiResult } from '@/common/decorators/api-result.decorator' +import { IdParam } from '@/common/decorators/id-param.decorator' +import { ApiSecurityAuth } from '@/common/decorators/swagger.decorator' +import { Pagination } from '@/helper/paginate/pagination' +import { Permission } from '@/modules/auth/decorators/permission.decorator' +import { TaskEntity } from '@/modules/system/task/task.entity' -import { TaskDto, TaskQueryDto } from './task.dto'; -import { TaskService } from './task.service'; +import { TaskDto, TaskQueryDto } from './task.dto' +import { TaskService } from './task.service' export const Permissions = { LIST: 'system:task:list', @@ -29,7 +21,7 @@ export const Permissions = { ONCE: 'system:task:once', START: 'system:task:start', STOP: 'system:task:stop', -} as const; +} as const @ApiTags('System - 任务调度模块') @ApiSecurityAuth() @@ -42,16 +34,16 @@ export class TaskController { @ApiResult({ type: [TaskEntity] }) @Permission(Permissions.LIST) async list(@Query() dto: TaskQueryDto): Promise> { - return this.taskService.list(dto); + return this.taskService.list(dto) } @Post() @ApiOperation({ summary: '添加任务' }) @Permission(Permissions.CREATE) async create(@Body() dto: TaskDto): Promise { - const serviceCall = dto.service.split('.'); - await this.taskService.checkHasMissionMeta(serviceCall[0], serviceCall[1]); - await this.taskService.create(dto); + const serviceCall = dto.service.split('.') + await this.taskService.checkHasMissionMeta(serviceCall[0], serviceCall[1]) + await this.taskService.create(dto) } @Put(':id') @@ -61,9 +53,9 @@ export class TaskController { @IdParam() id: number, @Body() dto: Partial, ): Promise { - const serviceCall = dto.service.split('.'); - await this.taskService.checkHasMissionMeta(serviceCall[0], serviceCall[1]); - await this.taskService.update(id, dto); + const serviceCall = dto.service.split('.') + await this.taskService.checkHasMissionMeta(serviceCall[0], serviceCall[1]) + await this.taskService.update(id, dto) } @Get(':id') @@ -71,39 +63,39 @@ export class TaskController { @ApiResult({ type: TaskEntity }) @Permission(Permissions.READ) async info(@IdParam() id: number): Promise { - return this.taskService.info(id); + return this.taskService.info(id) } @Delete(':id') @ApiOperation({ summary: '删除任务' }) @Permission(Permissions.DELETE) async delete(@IdParam() id: number): Promise { - const task = await this.taskService.info(id); - await this.taskService.delete(task); + const task = await this.taskService.info(id) + await this.taskService.delete(task) } @Put(':id/once') @ApiOperation({ summary: '手动执行一次任务' }) @Permission(Permissions.ONCE) async once(@IdParam() id: number): Promise { - const task = await this.taskService.info(id); - await this.taskService.once(task); + const task = await this.taskService.info(id) + await this.taskService.once(task) } @Put(':id/stop') @ApiOperation({ summary: '停止任务' }) @Permission(Permissions.STOP) async stop(@IdParam() id: number): Promise { - const task = await this.taskService.info(id); - await this.taskService.stop(task); + const task = await this.taskService.info(id) + await this.taskService.stop(task) } @Put(':id/start') @ApiOperation({ summary: '启动任务' }) @Permission(Permissions.START) async start(@IdParam() id: number): Promise { - const task = await this.taskService.info(id); + const task = await this.taskService.info(id) - await this.taskService.start(task); + await this.taskService.start(task) } } diff --git a/apps/api/src/modules/system/task/task.dto.ts b/apps/api/src/modules/system/task/task.dto.ts index 35e8ad9..1bb043c 100644 --- a/apps/api/src/modules/system/task/task.dto.ts +++ b/apps/api/src/modules/system/task/task.dto.ts @@ -1,5 +1,5 @@ -import { BadRequestException } from '@nestjs/common'; -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { BadRequestException } from '@nestjs/common' +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' import { IsDateString, IsIn, @@ -14,29 +14,29 @@ import { ValidationArguments, ValidatorConstraint, ValidatorConstraintInterface, -} from 'class-validator'; -import * as parser from 'cron-parser'; -import { isEmpty } from 'lodash'; +} from 'class-validator' +import * as parser from 'cron-parser' +import { isEmpty } from 'lodash' -import { PageOptionsDto } from '@/common/dto/page-options.dto'; +import { PageOptionsDto } from '@/common/dto/page-options.dto' // cron 表达式验证,bull lib下引用了cron-parser @ValidatorConstraint({ name: 'isCronExpression', async: false }) export class IsCronExpression implements ValidatorConstraintInterface { - validate(value: string, args: ValidationArguments) { + validate(value: string, _args: ValidationArguments) { try { if (isEmpty(value)) { - throw new BadRequestException('cron expression is empty'); + throw new BadRequestException('cron expression is empty') } - parser.parseExpression(value); - return true; + parser.parseExpression(value) + return true } catch (e) { - return false; + return false } } defaultMessage(_args: ValidationArguments) { - return 'this cron expression ($value) invalid'; + return 'this cron expression ($value) invalid' } } @@ -45,79 +45,79 @@ export class TaskDto { @IsString() @MinLength(2) @MaxLength(50) - name: string; + name: string @ApiProperty({ description: '调用的服务' }) @IsString() @MinLength(1) - service: string; + service: string @ApiProperty({ description: '任务类别:cron | interval' }) @IsIn([0, 1]) - type: number; + type: number @ApiProperty({ description: '任务状态' }) @IsIn([0, 1]) - status: number; + status: number @ApiPropertyOptional({ description: '开始时间', type: Date }) @IsDateString() @ValidateIf((o) => !isEmpty(o.startTime)) - startTime: string; + startTime: string @ApiPropertyOptional({ description: '结束时间', type: Date }) @IsDateString() @ValidateIf((o) => !isEmpty(o.endTime)) - endTime: string; + endTime: string @ApiPropertyOptional({ description: '限制执行次数,负数则无限制', }) @IsOptional() @IsInt() - limit?: number = -1; + limit?: number = -1 @ApiProperty({ description: 'cron表达式' }) @Validate(IsCronExpression) @ValidateIf((o) => o.type === 0) - cron: string; + cron: string @ApiProperty({ description: '执行间隔,毫秒单位' }) @IsInt() @Min(100) @ValidateIf((o) => o.type === 1) - every?: number; + every?: number @ApiPropertyOptional({ description: '执行参数' }) @IsOptional() @IsString() - data?: string; + data?: string @ApiPropertyOptional({ description: '任务备注' }) @IsOptional() @IsString() - remark?: string; + remark?: string } export class TaskQueryDto extends PageOptionsDto { @ApiProperty({ description: '任务名称' }) @IsOptional() @IsString() - name?: string; + name?: string @ApiProperty({ description: '调用的服务' }) @IsOptional() @IsString() @MinLength(1) - service: string; + service: string @ApiProperty({ description: '任务类别:cron | interval' }) @IsOptional() @IsIn([0, 1]) - type?: number; + type?: number @ApiProperty({ description: '任务状态' }) @IsOptional() @IsIn([0, 1]) - status?: number; + status?: number } diff --git a/apps/api/src/modules/system/task/task.entity.ts b/apps/api/src/modules/system/task/task.entity.ts index 1db2513..ebfeeeb 100644 --- a/apps/api/src/modules/system/task/task.entity.ts +++ b/apps/api/src/modules/system/task/task.entity.ts @@ -1,59 +1,59 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { PrimaryGeneratedColumn, Column, Entity } from 'typeorm'; +import { ApiProperty } from '@nestjs/swagger' +import { PrimaryGeneratedColumn, Column, Entity } from 'typeorm' -import { AbstractEntity } from '@/common/entity/abstract.entity'; +import { AbstractEntity } from '@/common/entity/abstract.entity' @Entity({ name: 'sys_task' }) export class TaskEntity extends AbstractEntity { @PrimaryGeneratedColumn() @ApiProperty() - id: number; + id: number @Column({ type: 'varchar', length: 50, unique: true }) @ApiProperty({ description: '任务名' }) - name: string; + name: string @Column() @ApiProperty({ description: '任务标识' }) - service: string; + service: string @Column({ type: 'tinyint', default: 0 }) @ApiProperty({ description: '任务类型 0cron 1间隔' }) - type: number; + type: number @Column({ type: 'tinyint', default: 1 }) @ApiProperty({ description: '任务状态 0禁用 1启用' }) - status: number; + status: number @Column({ name: 'start_time', type: 'datetime', nullable: true }) @ApiProperty({ description: '开始时间' }) - startTime: Date; + startTime: Date @Column({ name: 'end_time', type: 'datetime', nullable: true }) @ApiProperty({ description: '结束时间' }) - endTime: Date; + endTime: Date @Column({ type: 'int', nullable: true, default: 0 }) @ApiProperty({ description: '间隔时间' }) - limit: number; + limit: number @Column({ nullable: true }) @ApiProperty({ description: 'cron表达式' }) - cron: string; + cron: string @Column({ type: 'int', nullable: true }) @ApiProperty({ description: '执行次数' }) - every: number; + every: number @Column({ type: 'text', nullable: true }) @ApiProperty({ description: '任务参数' }) - data: string; + data: string @Column({ name: 'job_opts', type: 'text', nullable: true }) @ApiProperty({ description: '任务配置' }) - jobOpts: string; + jobOpts: string @Column({ nullable: true }) @ApiProperty({ description: '任务描述' }) - remark: string; + remark: string } diff --git a/apps/api/src/modules/system/task/task.module.ts b/apps/api/src/modules/system/task/task.module.ts index a8e84e5..a57fcaa 100644 --- a/apps/api/src/modules/system/task/task.module.ts +++ b/apps/api/src/modules/system/task/task.module.ts @@ -1,21 +1,21 @@ -import { BullModule } from '@nestjs/bull'; -import { Module } from '@nestjs/common'; +import { BullModule } from '@nestjs/bull' +import { Module } from '@nestjs/common' -import { ConfigService } from '@nestjs/config'; -import { TypeOrmModule } from '@nestjs/typeorm'; +import { ConfigService } from '@nestjs/config' +import { TypeOrmModule } from '@nestjs/typeorm' -import { IRedisConfig } from '@/config'; +import { IRedisConfig } from '@/config' -import { LogModule } from '../log/log.module'; +import { LogModule } from '../log/log.module' -import { SYS_TASK_QUEUE_NAME, SYS_TASK_QUEUE_PREFIX } from './constant'; +import { SYS_TASK_QUEUE_NAME, SYS_TASK_QUEUE_PREFIX } from './constant' -import { TaskController } from './task.controller'; -import { TaskEntity } from './task.entity'; -import { TaskConsumer } from './task.processor'; -import { TaskService } from './task.service'; +import { TaskController } from './task.controller' +import { TaskEntity } from './task.entity' +import { TaskConsumer } from './task.processor' +import { TaskService } from './task.service' -const providers = [TaskService, TaskConsumer]; +const providers = [TaskService, TaskConsumer] @Module({ imports: [ diff --git a/apps/api/src/modules/system/task/task.processor.ts b/apps/api/src/modules/system/task/task.processor.ts index d406df9..4cc32f1 100644 --- a/apps/api/src/modules/system/task/task.processor.ts +++ b/apps/api/src/modules/system/task/task.processor.ts @@ -1,16 +1,16 @@ -import { OnQueueCompleted, Process, Processor } from '@nestjs/bull'; -import { Job } from 'bull'; +import { OnQueueCompleted, Process, Processor } from '@nestjs/bull' +import { Job } from 'bull' -import { TaskLogService } from '../log/services/task-log.service'; +import { TaskLogService } from '../log/services/task-log.service' -import { SYS_TASK_QUEUE_NAME } from './constant'; +import { SYS_TASK_QUEUE_NAME } from './constant' -import { TaskService } from './task.service'; +import { TaskService } from './task.service' export interface ExecuteData { - id: number; - args?: string | null; - service: string; + id: number + args?: string | null + service: string } @Processor(SYS_TASK_QUEUE_NAME) @@ -22,22 +22,22 @@ export class TaskConsumer { @Process() async handle(job: Job): Promise { - const startTime = Date.now(); - const { data } = job; + const startTime = Date.now() + const { data } = job try { - await this.taskService.callService(data.service, data.args); - const timing = Date.now() - startTime; + await this.taskService.callService(data.service, data.args) + const timing = Date.now() - startTime // 任务执行成功 - await this.taskLogService.create(data.id, 1, timing); + await this.taskLogService.create(data.id, 1, timing) } catch (e) { - const timing = Date.now() - startTime; + const timing = Date.now() - startTime // 执行失败 - await this.taskLogService.create(data.id, 0, timing, `${e}`); + await this.taskLogService.create(data.id, 0, timing, `${e}`) } } @OnQueueCompleted() onCompleted(job: Job) { - this.taskService.updateTaskCompleteStatus(job.data.id); + this.taskService.updateTaskCompleteStatus(job.data.id) } } diff --git a/apps/api/src/modules/system/task/task.service.ts b/apps/api/src/modules/system/task/task.service.ts index 9c2e974..0c9e2f1 100644 --- a/apps/api/src/modules/system/task/task.service.ts +++ b/apps/api/src/modules/system/task/task.service.ts @@ -1,40 +1,40 @@ -import { InjectRedis } from '@liaoliaots/nestjs-redis'; -import { InjectQueue } from '@nestjs/bull'; +import { InjectRedis } from '@liaoliaots/nestjs-redis' +import { InjectQueue } from '@nestjs/bull' import { BadRequestException, Injectable, Logger, NotFoundException, OnModuleInit, -} from '@nestjs/common'; -import { ModuleRef, Reflector } from '@nestjs/core'; -import { UnknownElementException } from '@nestjs/core/errors/exceptions/unknown-element.exception'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Queue } from 'bull'; -import Redis from 'ioredis'; -import { isEmpty } from 'lodash'; -import { Like, Repository } from 'typeorm'; +} from '@nestjs/common' +import { ModuleRef, Reflector } from '@nestjs/core' +import { UnknownElementException } from '@nestjs/core/errors/exceptions/unknown-element.exception' +import { InjectRepository } from '@nestjs/typeorm' +import { Queue } from 'bull' +import Redis from 'ioredis' +import { isEmpty } from 'lodash' +import { Like, Repository } from 'typeorm' -import { BusinessException } from '@/common/exceptions/biz.exception'; -import { ErrorEnum } from '@/constants/error-code.constant'; +import { BusinessException } from '@/common/exceptions/biz.exception' +import { ErrorEnum } from '@/constants/error-code.constant' -import { paginate } from '@/helper/paginate'; -import { Pagination } from '@/helper/paginate/pagination'; +import { paginate } from '@/helper/paginate' +import { Pagination } from '@/helper/paginate/pagination' -import { TaskEntity } from '@/modules/system/task/task.entity'; -import { MISSION_DECORATOR_KEY } from '@/modules/tasks/mission.decorator'; +import { TaskEntity } from '@/modules/system/task/task.entity' +import { MISSION_DECORATOR_KEY } from '@/modules/tasks/mission.decorator' import { SYS_TASK_QUEUE_NAME, SYS_TASK_QUEUE_PREFIX, TaskStatus, -} from './constant'; +} from './constant' -import { TaskDto, TaskQueryDto } from './task.dto'; +import { TaskDto, TaskQueryDto } from './task.dto' @Injectable() export class TaskService implements OnModuleInit { - private logger = new Logger(TaskService.name); + private logger = new Logger(TaskService.name) constructor( @InjectRepository(TaskEntity) @@ -49,24 +49,24 @@ export class TaskService implements OnModuleInit { * module init */ async onModuleInit() { - await this.initTask(); + await this.initTask() } /** * 初始化任务,系统启动前调用 */ async initTask(): Promise { - const initKey = `${SYS_TASK_QUEUE_PREFIX}:init`; + const initKey = `${SYS_TASK_QUEUE_PREFIX}:init` // 防止重复初始化 const result = await this.redis .multi() .setnx(initKey, new Date().getTime()) .expire(initKey, 60 * 30) - .exec(); + .exec() if (result[0][1] === 0) { // 存在锁则直接跳过防止重复初始化 - this.logger.log('Init task is lock', TaskService.name); - return; + this.logger.log('Init task is lock', TaskService.name) + return } const jobs = await this.taskQueue.getJobs([ 'active', @@ -75,20 +75,20 @@ export class TaskService implements OnModuleInit { 'paused', 'waiting', 'completed', - ]); + ]) jobs.forEach((j) => { - j.remove(); - }); + j.remove() + }) // 查找所有需要运行的任务 - const tasks = await this.taskRepository.findBy({ status: 1 }); + const tasks = await this.taskRepository.findBy({ status: 1 }) if (tasks && tasks.length > 0) { for (const t of tasks) { - await this.start(t); + await this.start(t) } } // 启动后释放锁 - await this.redis.del(initKey); + await this.redis.del(initKey) } async list({ @@ -107,9 +107,9 @@ export class TaskService implements OnModuleInit { ...(type ? { type } : null), ...(status ? { status } : null), }) - .orderBy('task.id', 'ASC'); + .orderBy('task.id', 'ASC') - return paginate(queryBuilder, { page, pageSize }); + return paginate(queryBuilder, { page, pageSize }) } /** @@ -119,12 +119,12 @@ export class TaskService implements OnModuleInit { const task = this.taskRepository .createQueryBuilder('task') .where({ id }) - .getOne(); + .getOne() if (!task) { - throw new NotFoundException('Task Not Found'); + throw new NotFoundException('Task Not Found') } - return task; + return task } /** @@ -132,10 +132,10 @@ export class TaskService implements OnModuleInit { */ async delete(task: TaskEntity): Promise { if (!task) { - throw new BadRequestException('Task is Empty'); + throw new BadRequestException('Task is Empty') } - await this.stop(task); - await this.taskRepository.delete(task.id); + await this.stop(task) + await this.taskRepository.delete(task.id) } /** @@ -146,29 +146,29 @@ export class TaskService implements OnModuleInit { await this.taskQueue.add( { id: task.id, service: task.service, args: task.data }, { jobId: task.id, removeOnComplete: true, removeOnFail: true }, - ); + ) } else { - throw new BadRequestException('Task is Empty'); + throw new BadRequestException('Task is Empty') } } async create(dto: TaskDto): Promise { - const result = await this.taskRepository.save(dto); - const task = await this.info(result.id); + const result = await this.taskRepository.save(dto) + const task = await this.info(result.id) if (result.status === 0) { - await this.stop(task); + await this.stop(task) } else if (result.status === TaskStatus.Activited) { - await this.start(task); + await this.start(task) } } async update(id: number, dto: Partial): Promise { - await this.taskRepository.update(id, dto); - const task = await this.info(id); + await this.taskRepository.update(id, dto) + const task = await this.info(id) if (task.status === 0) { - await this.stop(task); + await this.stop(task) } else if (task.status === TaskStatus.Activited) { - await this.start(task); + await this.start(task) } } @@ -177,48 +177,48 @@ export class TaskService implements OnModuleInit { */ async start(task: TaskEntity): Promise { if (!task) { - throw new BadRequestException('Task is Empty'); + throw new BadRequestException('Task is Empty') } // 先停掉之前存在的任务 - await this.stop(task); - let repeat: any; + await this.stop(task) + let repeat: any if (task.type === 1) { // 间隔 Repeat every millis (cron setting cannot be used together with this setting.) repeat = { every: task.every, - }; + } } else { // cron repeat = { cron: task.cron, - }; + } // Start date when the repeat job should start repeating (only with cron). if (task.startTime) { - repeat.startDate = task.startTime; + repeat.startDate = task.startTime } if (task.endTime) { - repeat.endDate = task.endTime; + repeat.endDate = task.endTime } } if (task.limit > 0) { - repeat.limit = task.limit; + repeat.limit = task.limit } const job = await this.taskQueue.add( { id: task.id, service: task.service, args: task.data }, { jobId: task.id, removeOnComplete: true, removeOnFail: true, repeat }, - ); + ) if (job && job.opts) { await this.taskRepository.update(task.id, { jobOpts: JSON.stringify(job.opts.repeat), status: 1, - }); + }) } else { // update status to 0,标识暂停任务,因为启动失败 - await job?.remove(); + await job?.remove() await this.taskRepository.update(task.id, { status: TaskStatus.Disabled, - }); - throw new BadRequestException('Task Start failed'); + }) + throw new BadRequestException('Task Start failed') } } @@ -227,14 +227,14 @@ export class TaskService implements OnModuleInit { */ async stop(task: TaskEntity): Promise { if (!task) { - throw new BadRequestException('Task is Empty'); + throw new BadRequestException('Task is Empty') } - const exist = await this.existJob(task.id.toString()); + const exist = await this.existJob(task.id.toString()) if (!exist) { await this.taskRepository.update(task.id, { status: TaskStatus.Disabled, - }); - return; + }) + return } const jobs = await this.taskQueue.getJobs([ 'active', @@ -243,14 +243,14 @@ export class TaskService implements OnModuleInit { 'paused', 'waiting', 'completed', - ]); + ]) jobs .filter((j) => j.data.id === task.id) .forEach(async (j) => { - await j.remove(); - }); + await j.remove() + }) - await this.taskRepository.update(task.id, { status: TaskStatus.Disabled }); + await this.taskRepository.update(task.id, { status: TaskStatus.Disabled }) // if (task.jobOpts) { // await this.app.queue.sys.removeRepeatable(JSON.parse(task.jobOpts)); // // update status @@ -263,26 +263,26 @@ export class TaskService implements OnModuleInit { */ async existJob(jobId: string): Promise { // https://github.com/OptimalBits/bull/blob/develop/REFERENCE.md#queueremoverepeatablebykey - const jobs = await this.taskQueue.getRepeatableJobs(); + const jobs = await this.taskQueue.getRepeatableJobs() const ids = jobs.map((e) => { - return e.id; - }); - return ids.includes(jobId); + return e.id + }) + return ids.includes(jobId) } /** * 更新是否已经完成,完成则移除该任务并修改状态 */ async updateTaskCompleteStatus(tid: number): Promise { - const jobs = await this.taskQueue.getRepeatableJobs(); - const task = await this.taskRepository.findOneBy({ id: tid }); + const jobs = await this.taskQueue.getRepeatableJobs() + const task = await this.taskRepository.findOneBy({ id: tid }) // 如果下次执行时间小于当前时间,则表示已经执行完成。 for (const job of jobs) { - const currentTime = new Date().getTime(); + const currentTime = new Date().getTime() if (job.id === tid.toString() && job.next < currentTime) { // 如果下次执行时间小于当前时间,则表示已经执行完成。 - await this.stop(task); - break; + await this.stop(task) + break } } } @@ -296,32 +296,32 @@ export class TaskService implements OnModuleInit { exec: string, ): Promise { try { - let service: any; + let service: any if (typeof nameOrInstance === 'string') { - service = await this.moduleRef.get(nameOrInstance, { strict: false }); + service = await this.moduleRef.get(nameOrInstance, { strict: false }) } else { - service = nameOrInstance; + service = nameOrInstance } // 所执行的任务不存在 if (!service || !(exec in service)) { - throw new NotFoundException('任务不存在'); + throw new NotFoundException('任务不存在') } // 检测是否有Mission注解 const hasMission = this.reflector.get( MISSION_DECORATOR_KEY, service.constructor, - ); + ) // 如果没有,则抛出错误 if (!hasMission) { - throw new BusinessException(ErrorEnum.INSECURE_MISSION); + throw new BusinessException(ErrorEnum.INSECURE_MISSION) } } catch (e) { if (e instanceof UnknownElementException) { // 任务不存在 - throw new NotFoundException('任务不存在'); + throw new NotFoundException('任务不存在') } else { // 其余错误则不处理,继续抛出 - throw e; + throw e } } } @@ -331,27 +331,27 @@ export class TaskService implements OnModuleInit { */ async callService(name: string, args: string): Promise { if (name) { - const [serviceName, methodName] = name.split('.'); + const [serviceName, methodName] = name.split('.') if (!methodName) { - throw new BadRequestException('serviceName define BadRequestException'); + throw new BadRequestException('serviceName define BadRequestException') } const service = await this.moduleRef.get(serviceName, { strict: false, - }); + }) // 安全注解检查 - await this.checkHasMissionMeta(service, methodName); + await this.checkHasMissionMeta(service, methodName) if (isEmpty(args)) { - await service[methodName](); + await service[methodName]() } else { // 参数安全判断 - const parseArgs = this.safeParse(args); + const parseArgs = this.safeParse(args) if (Array.isArray(parseArgs)) { // 数组形式则自动扩展成方法参数回掉 - await service[methodName](...parseArgs); + await service[methodName](...parseArgs) } else { - await service[methodName](parseArgs); + await service[methodName](parseArgs) } } } @@ -359,9 +359,9 @@ export class TaskService implements OnModuleInit { safeParse(args: string): unknown | string { try { - return JSON.parse(args); + return JSON.parse(args) } catch (e) { - return args; + return args } } } diff --git a/apps/api/src/modules/system/task/task.ts b/apps/api/src/modules/system/task/task.ts index 10c09b3..26d1e2c 100644 --- a/apps/api/src/modules/system/task/task.ts +++ b/apps/api/src/modules/system/task/task.ts @@ -1,2 +1,2 @@ -export const SYS_TASK_QUEUE_NAME = 'system:sys-task'; -export const SYS_TASK_QUEUE_PREFIX = 'system:sys:task'; +export const SYS_TASK_QUEUE_NAME = 'system:sys-task' +export const SYS_TASK_QUEUE_PREFIX = 'system:sys:task' diff --git a/apps/api/src/modules/tasks/jobs/email.job.ts b/apps/api/src/modules/tasks/jobs/email.job.ts index a71c21a..547d28e 100644 --- a/apps/api/src/modules/tasks/jobs/email.job.ts +++ b/apps/api/src/modules/tasks/jobs/email.job.ts @@ -1,8 +1,8 @@ -import { BadRequestException, Injectable, Logger } from '@nestjs/common'; +import { BadRequestException, Injectable, Logger } from '@nestjs/common' -import { MailerService } from '@/shared/mailer/mailer.service'; +import { MailerService } from '@/shared/mailer/mailer.service' -import { Mission } from '../mission.decorator'; +import { Mission } from '../mission.decorator' /** * Api接口请求类型任务 @@ -17,11 +17,11 @@ export class EmailJob { async send(config: any): Promise { if (config) { - const { to, subject, content } = config; - const result = await this.emailService.send(to, subject, content); - this.logger.log(result, EmailJob.name); + const { to, subject, content } = config + const result = await this.emailService.send(to, subject, content) + this.logger.log(result, EmailJob.name) } else { - throw new BadRequestException('Email send job param is empty'); + throw new BadRequestException('Email send job param is empty') } } } diff --git a/apps/api/src/modules/tasks/jobs/http-request.job.ts b/apps/api/src/modules/tasks/jobs/http-request.job.ts index c0a649d..2aaf493 100644 --- a/apps/api/src/modules/tasks/jobs/http-request.job.ts +++ b/apps/api/src/modules/tasks/jobs/http-request.job.ts @@ -1,7 +1,7 @@ -import { HttpService } from '@nestjs/axios'; -import { BadRequestException, Injectable, Logger } from '@nestjs/common'; +import { HttpService } from '@nestjs/axios' +import { BadRequestException, Injectable, Logger } from '@nestjs/common' -import { Mission } from '../mission.decorator'; +import { Mission } from '../mission.decorator' /** * Api接口请求类型任务 @@ -20,10 +20,10 @@ export class HttpRequestJob { */ async handle(config: unknown): Promise { if (config) { - const result = await this.httpService.request(config); - this.logger.log(result, HttpRequestJob.name); + const result = await this.httpService.request(config) + this.logger.log(result, HttpRequestJob.name) } else { - throw new BadRequestException('Http request job param is empty'); + throw new BadRequestException('Http request job param is empty') } } } diff --git a/apps/api/src/modules/tasks/jobs/log-clear.job.ts b/apps/api/src/modules/tasks/jobs/log-clear.job.ts index 6542831..ba7ee5b 100644 --- a/apps/api/src/modules/tasks/jobs/log-clear.job.ts +++ b/apps/api/src/modules/tasks/jobs/log-clear.job.ts @@ -1,9 +1,9 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common' -import { LoginLogService } from '@/modules/system/log/services/login-log.service'; -import { TaskLogService } from '@/modules/system/log/services/task-log.service'; +import { LoginLogService } from '@/modules/system/log/services/login-log.service' +import { TaskLogService } from '@/modules/system/log/services/task-log.service' -import { Mission } from '../mission.decorator'; +import { Mission } from '../mission.decorator' /** * 管理后台日志清理任务 @@ -17,10 +17,10 @@ export class LogClearJob { ) {} async clearLoginLog(): Promise { - await this.loginLogService.clearLog(); + await this.loginLogService.clearLog() } async clearTaskLog(): Promise { - await this.taskLogService.clearLog(); + await this.taskLogService.clearLog() } } diff --git a/apps/api/src/modules/tasks/mission.decorator.ts b/apps/api/src/modules/tasks/mission.decorator.ts index beef1b1..c14c7c9 100644 --- a/apps/api/src/modules/tasks/mission.decorator.ts +++ b/apps/api/src/modules/tasks/mission.decorator.ts @@ -1,8 +1,8 @@ -import { SetMetadata } from '@nestjs/common'; +import { SetMetadata } from '@nestjs/common' -export const MISSION_DECORATOR_KEY = 'decorator:mission'; +export const MISSION_DECORATOR_KEY = 'decorator:mission' /** * 定时任务标记,没有该任务标记的任务不会被执行,保证全局获取下的模块被安全执行 */ -export const Mission = () => SetMetadata(MISSION_DECORATOR_KEY, true); +export const Mission = () => SetMetadata(MISSION_DECORATOR_KEY, true) diff --git a/apps/api/src/modules/tasks/tasks.module.ts b/apps/api/src/modules/tasks/tasks.module.ts index 03b50c0..f4fd469 100644 --- a/apps/api/src/modules/tasks/tasks.module.ts +++ b/apps/api/src/modules/tasks/tasks.module.ts @@ -1,13 +1,13 @@ -import { DynamicModule, ExistingProvider, Module } from '@nestjs/common'; +import { DynamicModule, ExistingProvider, Module } from '@nestjs/common' -import { LogModule } from '@/modules/system/log/log.module'; -import { SystemModule } from '@/modules/system/system.module'; +import { LogModule } from '@/modules/system/log/log.module' +import { SystemModule } from '@/modules/system/system.module' -import { EmailJob } from './jobs/email.job'; -import { HttpRequestJob } from './jobs/http-request.job'; -import { LogClearJob } from './jobs/log-clear.job'; +import { EmailJob } from './jobs/email.job' +import { HttpRequestJob } from './jobs/http-request.job' +import { LogClearJob } from './jobs/log-clear.job' -const providers = [LogClearJob, HttpRequestJob, EmailJob]; +const providers = [LogClearJob, HttpRequestJob, EmailJob] /** * auto create alias @@ -17,14 +17,14 @@ const providers = [LogClearJob, HttpRequestJob, EmailJob]; * } */ function createAliasProviders(): ExistingProvider[] { - const aliasProviders: ExistingProvider[] = []; + const aliasProviders: ExistingProvider[] = [] for (const p of providers) { aliasProviders.push({ provide: p.name, useExisting: p, - }); + }) } - return aliasProviders; + return aliasProviders } /** @@ -34,13 +34,13 @@ function createAliasProviders(): ExistingProvider[] { export class TasksModule { static forRoot(): DynamicModule { // 使用Alias定义别名,使得可以通过字符串类型获取定义的Service,否则无法获取 - const aliasProviders = createAliasProviders(); + const aliasProviders = createAliasProviders() return { global: true, module: TasksModule, imports: [SystemModule, LogModule], providers: [...providers, ...aliasProviders], exports: aliasProviders, - }; + } } } diff --git a/apps/api/src/modules/todo/todo.controller.ts b/apps/api/src/modules/todo/todo.controller.ts index 7c53409..cb09d83 100644 --- a/apps/api/src/modules/todo/todo.controller.ts +++ b/apps/api/src/modules/todo/todo.controller.ts @@ -6,20 +6,20 @@ import { Put, Delete, UseGuards, -} from '@nestjs/common'; -import { ApiTags, ApiOperation } from '@nestjs/swagger'; +} from '@nestjs/common' +import { ApiTags, ApiOperation } from '@nestjs/swagger' -import { ApiResult } from '@/common/decorators/api-result.decorator'; -import { IdParam } from '@/common/decorators/id-param.decorator'; +import { ApiResult } from '@/common/decorators/api-result.decorator' +import { IdParam } from '@/common/decorators/id-param.decorator' -import { Permission } from '@/modules/auth/decorators/permission.decorator'; -import { Resource } from '@/modules/auth/decorators/resource.decorator'; +import { Permission } from '@/modules/auth/decorators/permission.decorator' +import { Resource } from '@/modules/auth/decorators/resource.decorator' -import { ResourceGuard } from '@/modules/auth/guards/resource.guard'; -import { TodoEntity } from '@/modules/todo/todo.entity'; +import { ResourceGuard } from '@/modules/auth/guards/resource.guard' +import { TodoEntity } from '@/modules/todo/todo.entity' -import { TodoDto } from './todo.dto'; -import { TodoService } from './todo.service'; +import { TodoDto } from './todo.dto' +import { TodoService } from './todo.service' export const Permissions = { LIST: 'todo:list', @@ -27,7 +27,7 @@ export const Permissions = { READ: 'todo:read', UPDATE: 'todo:update', DELETE: 'todo:delete', -} as const; +} as const @ApiTags('Business - Todo模块') @UseGuards(ResourceGuard) @@ -40,7 +40,7 @@ export class TodoController { @ApiResult({ type: [TodoEntity] }) @Permission(Permissions.LIST) async list(): Promise { - return this.todoService.list(); + return this.todoService.list() } @Get(':id') @@ -48,14 +48,14 @@ export class TodoController { @ApiResult({ type: TodoEntity }) @Permission(Permissions.READ) async info(@IdParam() id: number): Promise { - return this.todoService.detail(id); + return this.todoService.detail(id) } @Post() @ApiOperation({ summary: '创建Todo' }) @Permission(Permissions.CREATE) async create(@Body() dto: TodoDto): Promise { - await this.todoService.create(dto); + await this.todoService.create(dto) } @Put(':id') @@ -66,7 +66,7 @@ export class TodoController { @IdParam() id: number, @Body() dto: Partial, ): Promise { - await this.todoService.update(id, dto); + await this.todoService.update(id, dto) } @Delete(':id') @@ -74,6 +74,6 @@ export class TodoController { @Permission(Permissions.DELETE) @Resource(TodoEntity) async delete(@IdParam() id: number): Promise { - await this.todoService.delete(id); + await this.todoService.delete(id) } } diff --git a/apps/api/src/modules/todo/todo.dto.ts b/apps/api/src/modules/todo/todo.dto.ts index e7de581..30bcc25 100644 --- a/apps/api/src/modules/todo/todo.dto.ts +++ b/apps/api/src/modules/todo/todo.dto.ts @@ -1,12 +1,12 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsString } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger' +import { IsString } from 'class-validator' -import { PageOptionsDto } from '@/common/dto/page-options.dto'; +import { PageOptionsDto } from '@/common/dto/page-options.dto' export class TodoDto { @ApiProperty({ description: '名称' }) @IsString() - value: string; + value: string } export class TodoQueryDto extends PageOptionsDto {} diff --git a/apps/api/src/modules/todo/todo.entity.ts b/apps/api/src/modules/todo/todo.entity.ts index 9a0c50c..37e6287 100644 --- a/apps/api/src/modules/todo/todo.entity.ts +++ b/apps/api/src/modules/todo/todo.entity.ts @@ -1,20 +1,20 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm'; +import { ApiProperty } from '@nestjs/swagger' +import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm' -import { AbstractEntity } from '@/common/entity/abstract.entity'; -import { UserEntity } from '@/modules/user/entities/user.entity'; +import { AbstractEntity } from '@/common/entity/abstract.entity' +import { UserEntity } from '@/modules/user/entities/user.entity' @Entity('todo') export class TodoEntity extends AbstractEntity { @Column() @ApiProperty({ description: 'todo' }) - value: string; + value: string @ApiProperty({ description: 'todo' }) @Column({ default: false }) - status: boolean; + status: boolean @ManyToOne(() => UserEntity) @JoinColumn() - user: UserEntity; + user: UserEntity } diff --git a/apps/api/src/modules/todo/todo.module.ts b/apps/api/src/modules/todo/todo.module.ts index 78ee8ff..5a3d417 100644 --- a/apps/api/src/modules/todo/todo.module.ts +++ b/apps/api/src/modules/todo/todo.module.ts @@ -1,11 +1,11 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; +import { Module } from '@nestjs/common' +import { TypeOrmModule } from '@nestjs/typeorm' -import { TodoController } from './todo.controller'; -import { TodoEntity } from './todo.entity'; -import { TodoService } from './todo.service'; +import { TodoController } from './todo.controller' +import { TodoEntity } from './todo.entity' +import { TodoService } from './todo.service' -const services = [TodoService]; +const services = [TodoService] @Module({ imports: [TypeOrmModule.forFeature([TodoEntity])], diff --git a/apps/api/src/modules/todo/todo.service.ts b/apps/api/src/modules/todo/todo.service.ts index 8bdfada..8c6d8ee 100644 --- a/apps/api/src/modules/todo/todo.service.ts +++ b/apps/api/src/modules/todo/todo.service.ts @@ -1,12 +1,12 @@ -import { Injectable, NotFoundException } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; +import { Injectable, NotFoundException } from '@nestjs/common' +import { InjectRepository } from '@nestjs/typeorm' +import { Repository } from 'typeorm' -import { paginate } from '@/helper/paginate'; -import { Pagination } from '@/helper/paginate/pagination'; -import { TodoEntity } from '@/modules/todo/todo.entity'; +import { paginate } from '@/helper/paginate' +import { Pagination } from '@/helper/paginate/pagination' +import { TodoEntity } from '@/modules/todo/todo.entity' -import { TodoDto, TodoQueryDto } from './todo.dto'; +import { TodoDto, TodoQueryDto } from './todo.dto' @Injectable() export class TodoService { @@ -16,37 +16,37 @@ export class TodoService { ) {} async list(): Promise { - return this.todoRepository.find(); + return this.todoRepository.find() } async page({ page, pageSize, }: TodoQueryDto): Promise> { - return paginate(this.todoRepository, { page, pageSize }); + return paginate(this.todoRepository, { page, pageSize }) } async detail(id: number): Promise { - const item = await this.todoRepository.findOneBy({ id }); - if (!item) throw new NotFoundException('未找到该记录'); + const item = await this.todoRepository.findOneBy({ id }) + if (!item) throw new NotFoundException('未找到该记录') - return item; + return item } async create(dto: TodoDto) { - let test = new TodoEntity(); - test = Object.assign(dto); + let test = new TodoEntity() + test = Object.assign(dto) - await this.todoRepository.save(test); + await this.todoRepository.save(test) } async update(id: number, dto: Partial) { - await this.todoRepository.update(id, dto); + await this.todoRepository.update(id, dto) } async delete(id: number) { - const item = await this.detail(id); + const item = await this.detail(id) - await this.todoRepository.remove(item); + await this.todoRepository.remove(item) } } diff --git a/apps/api/src/modules/tools/email/email.controller.ts b/apps/api/src/modules/tools/email/email.controller.ts index 405a740..15edc71 100644 --- a/apps/api/src/modules/tools/email/email.controller.ts +++ b/apps/api/src/modules/tools/email/email.controller.ts @@ -1,11 +1,11 @@ -import { Body, Controller, Post } from '@nestjs/common'; +import { Body, Controller, Post } from '@nestjs/common' -import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { ApiOperation, ApiTags } from '@nestjs/swagger' -import { ApiSecurityAuth } from '@/common/decorators/swagger.decorator'; -import { MailerService } from '@/shared/mailer/mailer.service'; +import { ApiSecurityAuth } from '@/common/decorators/swagger.decorator' +import { MailerService } from '@/shared/mailer/mailer.service' -import { EmailSendDto } from './email.dto'; +import { EmailSendDto } from './email.dto' @ApiTags('System - 邮箱模块') @ApiSecurityAuth() @@ -16,7 +16,7 @@ export class EmailController { @ApiOperation({ summary: '发送邮件' }) @Post('send') async send(@Body() dto: EmailSendDto): Promise { - const { to, subject, content } = dto; - await this.emailService.sendMailHtml(to, subject, content); + const { to, subject, content } = dto + await this.emailService.sendMailHtml(to, subject, content) } } diff --git a/apps/api/src/modules/tools/email/email.dto.ts b/apps/api/src/modules/tools/email/email.dto.ts index 1db5621..656a781 100644 --- a/apps/api/src/modules/tools/email/email.dto.ts +++ b/apps/api/src/modules/tools/email/email.dto.ts @@ -1,5 +1,5 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsString } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger' +import { IsString } from 'class-validator' /** * 发送邮件 @@ -7,13 +7,13 @@ import { IsString } from 'class-validator'; export class EmailSendDto { @ApiProperty({ description: '收件人邮箱' }) @IsString() - to: string; + to: string @ApiProperty({ description: '标题' }) @IsString() - subject: number; + subject: number @ApiProperty({ description: '正文' }) @IsString() - content: string; + content: string } diff --git a/apps/api/src/modules/tools/storage/storage.controller.ts b/apps/api/src/modules/tools/storage/storage.controller.ts index 7e732d1..f4ea302 100644 --- a/apps/api/src/modules/tools/storage/storage.controller.ts +++ b/apps/api/src/modules/tools/storage/storage.controller.ts @@ -1,18 +1,18 @@ -import { Body, Controller, Get, Post, Query } from '@nestjs/common'; +import { Body, Controller, Get, Post, Query } from '@nestjs/common' -import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { ApiOperation, ApiTags } from '@nestjs/swagger' -import { ApiResult } from '@/common/decorators/api-result.decorator'; -import { ApiSecurityAuth } from '@/common/decorators/swagger.decorator'; +import { ApiResult } from '@/common/decorators/api-result.decorator' +import { ApiSecurityAuth } from '@/common/decorators/swagger.decorator' -import { Pagination } from '@/helper/paginate/pagination'; +import { Pagination } from '@/helper/paginate/pagination' -import { Permission } from '@/modules/auth/decorators/permission.decorator'; +import { Permission } from '@/modules/auth/decorators/permission.decorator' -import { StorageDeleteDto, StoragePageDto } from './storage.dto'; -import { StorageInfo } from './storage.modal'; +import { StorageDeleteDto, StoragePageDto } from './storage.dto' +import { StorageInfo } from './storage.modal' -import { StorageService } from './storage.service'; +import { StorageService } from './storage.service' @ApiTags('Tools - 存储模块') @ApiSecurityAuth() @@ -25,13 +25,13 @@ export class StorageController { @ApiResult({ type: StorageInfo, isPage: true }) @Permission('tool:storage:list') async list(@Query() dto: StoragePageDto): Promise> { - return this.storageService.list(dto); + return this.storageService.list(dto) } @ApiOperation({ summary: '删除文件' }) @Post('delete') @Permission('tool:storage:delete') async delete(@Body() dto: StorageDeleteDto): Promise { - await this.storageService.delete(dto.ids); + await this.storageService.delete(dto.ids) } } diff --git a/apps/api/src/modules/tools/storage/storage.dto.ts b/apps/api/src/modules/tools/storage/storage.dto.ts index fdfd09b..a3d42fb 100644 --- a/apps/api/src/modules/tools/storage/storage.dto.ts +++ b/apps/api/src/modules/tools/storage/storage.dto.ts @@ -1,68 +1,68 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { ArrayNotEmpty, IsArray, IsOptional, IsString } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger' +import { ArrayNotEmpty, IsArray, IsOptional, IsString } from 'class-validator' -import { PageOptionsDto } from '@/common/dto/page-options.dto'; +import { PageOptionsDto } from '@/common/dto/page-options.dto' export class StoragePageDto extends PageOptionsDto { @ApiProperty({ description: '文件名' }) @IsOptional() @IsString() - name: string; + name: string @ApiProperty({ description: '文件后缀' }) @IsString() @IsOptional() - extName: string; + extName: string @ApiProperty({ description: '文件类型' }) @IsString() @IsOptional() - type: string; + type: string @ApiProperty({ description: '大小' }) @IsString() @IsOptional() - size: string; + size: string @ApiProperty({ description: '上传时间' }) @IsOptional() - time: string[]; + time: string[] @ApiProperty({ description: '上传者' }) @IsString() @IsOptional() - username: string; + username: string } export class StorageCreateDto { @ApiProperty({ description: '文件名' }) @IsString() - name: string; + name: string @ApiProperty({ description: '真实文件名' }) @IsString() - fileName: string; + fileName: string @ApiProperty({ description: '文件扩展名' }) @IsString() - extName: string; + extName: string @ApiProperty({ description: '文件路径' }) @IsString() - path: string; + path: string @ApiProperty({ description: '文件路径' }) @IsString() - type: string; + type: string @ApiProperty({ description: '文件大小' }) @IsString() - size: string; + size: string } export class StorageDeleteDto { @ApiProperty({ description: '需要删除的文件ID列表', type: [Number] }) @IsArray() @ArrayNotEmpty() - ids: number[]; + ids: number[] } diff --git a/apps/api/src/modules/tools/storage/storage.entity.ts b/apps/api/src/modules/tools/storage/storage.entity.ts index 075e6d5..a25cda2 100644 --- a/apps/api/src/modules/tools/storage/storage.entity.ts +++ b/apps/api/src/modules/tools/storage/storage.entity.ts @@ -1,17 +1,17 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { PrimaryGeneratedColumn, Column, Entity } from 'typeorm'; +import { ApiProperty } from '@nestjs/swagger' +import { PrimaryGeneratedColumn, Column, Entity } from 'typeorm' -import { AbstractEntity } from '@/common/entity/abstract.entity'; +import { AbstractEntity } from '@/common/entity/abstract.entity' @Entity({ name: 'tool_storage' }) export class Storage extends AbstractEntity { @PrimaryGeneratedColumn() @ApiProperty() - id: number; + id: number @Column({ type: 'varchar', length: 200, comment: '文件名' }) @ApiProperty({ description: '文件名' }) - name: string; + name: string @Column({ type: 'varchar', @@ -20,25 +20,25 @@ export class Storage extends AbstractEntity { comment: '真实文件名', }) @ApiProperty({ description: '真实文件名' }) - fileName: string; + fileName: string @Column({ name: 'ext_name', type: 'varchar', nullable: true }) @ApiProperty({ description: '扩展名' }) - extName: string; + extName: string @Column({ type: 'varchar' }) @ApiProperty({ description: '文件类型' }) - path: string; + path: string @Column({ type: 'varchar', nullable: true }) @ApiProperty({ description: '文件类型' }) - type: string; + type: string @Column({ type: 'varchar', nullable: true }) @ApiProperty({ description: '文件大小' }) - size: string; + size: string @Column({ nullable: true, name: 'user_id' }) @ApiProperty({ description: '用户ID' }) - userId: number; + userId: number } diff --git a/apps/api/src/modules/tools/storage/storage.modal.ts b/apps/api/src/modules/tools/storage/storage.modal.ts index 43ede4e..203e974 100644 --- a/apps/api/src/modules/tools/storage/storage.modal.ts +++ b/apps/api/src/modules/tools/storage/storage.modal.ts @@ -1,27 +1,27 @@ -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty } from '@nestjs/swagger' export class StorageInfo { @ApiProperty({ description: '文件ID' }) - id: number; + id: number @ApiProperty({ description: '文件名' }) - name: string; + name: string @ApiProperty({ description: '文件扩展名' }) - extName: string; + extName: string @ApiProperty({ description: '文件路径' }) - path: string; + path: string @ApiProperty({ description: '文件类型' }) - type: string; + type: string @ApiProperty({ description: '大小' }) - size: string; + size: string @ApiProperty({ description: '上传时间' }) - createdAt: string; + createdAt: string @ApiProperty({ description: '上传者' }) - username: string; + username: string } diff --git a/apps/api/src/modules/tools/storage/storage.service.ts b/apps/api/src/modules/tools/storage/storage.service.ts index e0ece1c..1081ff8 100644 --- a/apps/api/src/modules/tools/storage/storage.service.ts +++ b/apps/api/src/modules/tools/storage/storage.service.ts @@ -1,17 +1,17 @@ -import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Between, Like, Repository } from 'typeorm'; +import { Injectable } from '@nestjs/common' +import { InjectRepository } from '@nestjs/typeorm' +import { Between, Like, Repository } from 'typeorm' -import { paginateRaw } from '@/helper/paginate'; -import { PaginationTypeEnum } from '@/helper/paginate/interface'; -import { Pagination } from '@/helper/paginate/pagination'; -import { Storage } from '@/modules/tools/storage/storage.entity'; -import { UserEntity } from '@/modules/user/entities/user.entity'; +import { paginateRaw } from '@/helper/paginate' +import { PaginationTypeEnum } from '@/helper/paginate/interface' +import { Pagination } from '@/helper/paginate/pagination' +import { Storage } from '@/modules/tools/storage/storage.entity' +import { UserEntity } from '@/modules/user/entities/user.entity' -import { deleteFile } from '@/utils/file'; +import { deleteFile } from '@/utils/file' -import { StorageCreateDto, StoragePageDto } from './storage.dto'; -import { StorageInfo } from './storage.modal'; +import { StorageCreateDto, StoragePageDto } from './storage.dto' +import { StorageInfo } from './storage.modal' @Injectable() export class StorageService { @@ -26,19 +26,19 @@ export class StorageService { await this.storageRepository.save({ ...dto, userId, - }); + }) } /** * 删除文件 */ async delete(fileIds: number[]): Promise { - const items = await this.storageRepository.findByIds(fileIds); - await this.storageRepository.delete(fileIds); + const items = await this.storageRepository.findByIds(fileIds) + await this.storageRepository.delete(fileIds) items.forEach((el) => { - deleteFile(el.path); - }); + deleteFile(el.path) + }) } async list({ @@ -64,13 +64,13 @@ export class StorageService { userId: await (await this.userRepository.findOneBy({ username })).id, }), }) - .orderBy('storage.created_at', 'DESC'); + .orderBy('storage.created_at', 'DESC') const { items, ...rest } = await paginateRaw(queryBuilder, { page, pageSize, paginationType: PaginationTypeEnum.LIMIT_AND_OFFSET, - }); + }) function formatResult(result: Storage[]) { return result.map((e: any) => { @@ -83,17 +83,17 @@ export class StorageService { size: e.storage_size, createdAt: e.storage_created_at, username: e.user_username, - }; - }); + } + }) } return { items: formatResult(items), ...rest, - }; + } } async count(): Promise { - return this.storageRepository.count(); + return this.storageRepository.count() } } diff --git a/apps/api/src/modules/tools/tools.module.ts b/apps/api/src/modules/tools/tools.module.ts index 1a58a51..32a8487 100644 --- a/apps/api/src/modules/tools/tools.module.ts +++ b/apps/api/src/modules/tools/tools.module.ts @@ -1,16 +1,16 @@ -import { Module } from '@nestjs/common'; +import { Module } from '@nestjs/common' -import { TypeOrmModule } from '@nestjs/typeorm'; +import { TypeOrmModule } from '@nestjs/typeorm' -import { UserEntity } from '../user/entities/user.entity'; +import { UserEntity } from '../user/entities/user.entity' -import { EmailController } from './email/email.controller'; -import { StorageController } from './storage/storage.controller'; +import { EmailController } from './email/email.controller' +import { StorageController } from './storage/storage.controller' -import { Storage } from './storage/storage.entity'; -import { StorageService } from './storage/storage.service'; -import { UploadController } from './upload/upload.controller'; -import { UploadService } from './upload/upload.service'; +import { Storage } from './storage/storage.entity' +import { StorageService } from './storage/storage.service' +import { UploadController } from './upload/upload.controller' +import { UploadService } from './upload/upload.service' @Module({ imports: [TypeOrmModule.forFeature([Storage, UserEntity])], diff --git a/apps/api/src/modules/tools/upload/file.constraint.ts b/apps/api/src/modules/tools/upload/file.constraint.ts index a9d0695..4c20cad 100644 --- a/apps/api/src/modules/tools/upload/file.constraint.ts +++ b/apps/api/src/modules/tools/upload/file.constraint.ts @@ -1,45 +1,45 @@ /* eslint-disable no-underscore-dangle */ -import { FastifyMultipartBaseOptions, MultipartFile } from '@fastify/multipart'; +import { FastifyMultipartBaseOptions, MultipartFile } from '@fastify/multipart' import { registerDecorator, ValidationArguments, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface, -} from 'class-validator'; -import { has, isArray } from 'lodash'; +} from 'class-validator' +import { has, isArray } from 'lodash' type FileLimit = Pick< FastifyMultipartBaseOptions['limits'], 'fileSize' | 'files' > & { - mimetypes?: string[]; -}; + mimetypes?: string[] +} const checkFileAndLimit = (file: MultipartFile, limits: FileLimit = {}) => { - if (!('mimetype' in file)) return false; + if (!('mimetype' in file)) return false if (limits.mimetypes && !limits.mimetypes.includes(file.mimetype)) - return false; + return false if ( has(file, '_buf') && Buffer.byteLength((file as any)._buf) > limits.fileSize ) - return false; - return true; -}; + return false + return true +} @ValidatorConstraint({ name: 'isFile' }) export class FileConstraint implements ValidatorConstraintInterface { validate(value: MultipartFile, args: ValidationArguments) { - const [limits = {}] = args.constraints; - const values = (args.object as any)[args.property]; - const filesLimit = (limits as FileLimit).files ?? 0; + const [limits = {}] = args.constraints + const values = (args.object as any)[args.property] + const filesLimit = (limits as FileLimit).files ?? 0 if (filesLimit > 0 && isArray(values) && values.length > filesLimit) - return false; - return checkFileAndLimit(value, limits); + return false + return checkFileAndLimit(value, limits) } - defaultMessage(args: ValidationArguments) { - return `The file which to upload's conditions are not met`; + defaultMessage(_args: ValidationArguments) { + return `The file which to upload's conditions are not met` } } @@ -59,6 +59,6 @@ export function IsFile( options: validationOptions, constraints: [limits], validator: FileConstraint, - }); - }; + }) + } } diff --git a/apps/api/src/modules/tools/upload/upload.controller.ts b/apps/api/src/modules/tools/upload/upload.controller.ts index 5b35cf0..e540370 100644 --- a/apps/api/src/modules/tools/upload/upload.controller.ts +++ b/apps/api/src/modules/tools/upload/upload.controller.ts @@ -1,13 +1,13 @@ -import { MultipartFile } from '@fastify/multipart'; -import { BadRequestException, Body, Controller, Post } from '@nestjs/common'; -import { ApiConsumes, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { MultipartFile } from '@fastify/multipart' +import { BadRequestException, Body, Controller, Post } from '@nestjs/common' +import { ApiConsumes, ApiOperation, ApiTags } from '@nestjs/swagger' -import { ApiSecurityAuth } from '@/common/decorators/swagger.decorator'; -import { AuthUser } from '@/modules/auth/decorators/auth-user.decorator'; +import { ApiSecurityAuth } from '@/common/decorators/swagger.decorator' +import { AuthUser } from '@/modules/auth/decorators/auth-user.decorator' -import { Permission } from '@/modules/auth/decorators/permission.decorator'; +import { Permission } from '@/modules/auth/decorators/permission.decorator' -import { UploadService } from './upload.service'; +import { UploadService } from './upload.service' @ApiSecurityAuth() @ApiTags('Tools - 上传模块') @@ -23,17 +23,17 @@ export class UploadController { @Body() dto: { file: MultipartFile }, @AuthUser() user: IAuthUser, ) { - const { file } = dto; + const { file } = dto try { - const path = await this.uploadService.saveFile(file, user.uid); + const path = await this.uploadService.saveFile(file, user.uid) return { filename: path, - }; + } } catch (error) { - console.log(error); - throw new BadRequestException('上传失败'); + console.log(error) + throw new BadRequestException('上传失败') } } } diff --git a/apps/api/src/modules/tools/upload/upload.dto.ts b/apps/api/src/modules/tools/upload/upload.dto.ts index 46fe698..fe5045a 100644 --- a/apps/api/src/modules/tools/upload/upload.dto.ts +++ b/apps/api/src/modules/tools/upload/upload.dto.ts @@ -1,9 +1,9 @@ -import { MultipartFile } from '@fastify/multipart'; -import { ApiProperty } from '@nestjs/swagger'; +import { MultipartFile } from '@fastify/multipart' +import { ApiProperty } from '@nestjs/swagger' -import { IsDefined } from 'class-validator'; +import { IsDefined } from 'class-validator' -import { IsFile } from './file.constraint'; +import { IsFile } from './file.constraint' export class FileUploadDto { @ApiProperty({ type: 'string', format: 'binary', description: '文件' }) @@ -23,5 +23,5 @@ export class FileUploadDto { message: '文件类型不正确', }, ) - file: MultipartFile; + file: MultipartFile } diff --git a/apps/api/src/modules/tools/upload/upload.service.ts b/apps/api/src/modules/tools/upload/upload.service.ts index a0e2eee..17b7df1 100644 --- a/apps/api/src/modules/tools/upload/upload.service.ts +++ b/apps/api/src/modules/tools/upload/upload.service.ts @@ -1,10 +1,10 @@ -import { MultipartFile } from '@fastify/multipart'; -import { Injectable, NotFoundException } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { isNil } from 'lodash'; -import { Repository } from 'typeorm'; +import { MultipartFile } from '@fastify/multipart' +import { Injectable, NotFoundException } from '@nestjs/common' +import { InjectRepository } from '@nestjs/typeorm' +import { isNil } from 'lodash' +import { Repository } from 'typeorm' -import { Storage } from '@/modules/tools/storage/storage.entity'; +import { Storage } from '@/modules/tools/storage/storage.entity' import { fileRename, @@ -13,7 +13,7 @@ import { getFileType, getSize, saveLocalFile, -} from '@/utils/file'; +} from '@/utils/file' @Injectable() export class UploadService { @@ -26,17 +26,16 @@ export class UploadService { * 保存文件上传记录 */ async saveFile(file: MultipartFile, userId: number): Promise { - if (isNil(file)) - throw new NotFoundException('Have not any file to upload!'); + if (isNil(file)) throw new NotFoundException('Have not any file to upload!') - const fileName = file.filename; - const size = getSize(file.file.bytesRead); - const extName = getExtname(fileName); - const type = getFileType(extName); - const name = fileRename(fileName); - const path = getFilePath(name); + const fileName = file.filename + const size = getSize(file.file.bytesRead) + const extName = getExtname(fileName) + const type = getFileType(extName) + const name = fileRename(fileName) + const path = getFilePath(name) - saveLocalFile(await file.toBuffer(), name); + saveLocalFile(await file.toBuffer(), name) await this.storageRepository.save({ name, @@ -46,8 +45,8 @@ export class UploadService { type, size, userId, - }); + }) - return path; + return path } } diff --git a/apps/api/src/modules/user/dto/password.dto.ts b/apps/api/src/modules/user/dto/password.dto.ts index ba3d895..1a05541 100644 --- a/apps/api/src/modules/user/dto/password.dto.ts +++ b/apps/api/src/modules/user/dto/password.dto.ts @@ -1,14 +1,8 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { - IsInt, - IsString, - Matches, - MaxLength, - MinLength, -} from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger' +import { IsInt, IsString, Matches, MaxLength, MinLength } from 'class-validator' -import { UserEntity } from '@/modules/user/entities/user.entity'; -import { IsEntityExist } from '@/shared/database/constraints/entity-exist.constraint'; +import { UserEntity } from '@/modules/user/entities/user.entity' +import { IsEntityExist } from '@/shared/database/constraints/entity-exist.constraint' export class PasswordUpdateDto { @ApiProperty({ description: '旧密码' }) @@ -16,26 +10,26 @@ export class PasswordUpdateDto { @Matches(/^[a-z0-9A-Z\W_]+$/) @MinLength(6) @MaxLength(20) - oldPassword: string; + oldPassword: string @ApiProperty({ description: '新密码' }) @Matches(/^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Za-z])\S*$/, { message: '密码必须包含数字、字母,长度为6-16', }) - newPassword: string; + newPassword: string } export class UserPasswordDto { @ApiProperty({ description: '管理员/用户ID' }) @IsEntityExist(UserEntity, { message: '用户不存在' }) @IsInt() - id: number; + id: number @ApiProperty({ description: '更改后的密码' }) @Matches(/^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Za-z])\S*$/, { message: '密码格式不正确', }) - password: string; + password: string } export class UserExistDto { @@ -44,5 +38,5 @@ export class UserExistDto { @Matches(/^[a-zA-Z0-9_-]{4,16}$/) @MinLength(6) @MaxLength(20) - username: string; + username: string } diff --git a/apps/api/src/modules/user/dto/user.dto.ts b/apps/api/src/modules/user/dto/user.dto.ts index cbb696b..6ee6e5f 100644 --- a/apps/api/src/modules/user/dto/user.dto.ts +++ b/apps/api/src/modules/user/dto/user.dto.ts @@ -1,5 +1,5 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; +import { ApiProperty } from '@nestjs/swagger' +import { Type } from 'class-transformer' import { ArrayMaxSize, ArrayMinSize, @@ -13,12 +13,12 @@ import { MaxLength, MinLength, ValidateIf, -} from 'class-validator'; -import { isEmpty } from 'lodash'; +} from 'class-validator' +import { isEmpty } from 'lodash' -import { PageOptionsDto } from '@/common/dto/page-options.dto'; -import { UserEntity } from '@/modules/user/entities/user.entity'; -import { IsUnique } from '@/shared/database/constraints/unique.constraint'; +import { PageOptionsDto } from '@/common/dto/page-options.dto' +import { UserEntity } from '@/modules/user/entities/user.entity' +import { IsUnique } from '@/shared/database/constraints/unique.constraint' export class UserDto { @ApiProperty({ description: '登录账号', example: 'kz-admin' }) @@ -26,42 +26,42 @@ export class UserDto { @Matches(/^[a-z0-9A-Z\W_]+$/) @MinLength(4) @MaxLength(20) - username: string; + username: string @ApiProperty({ description: '登录密码', example: 'a123456' }) @IsOptional() @Matches(/^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Za-z])\S*$/, { message: '密码必须包含数字、字母,长度为6-16', }) - password: string; + password: string @ApiProperty({ description: '归属角色', type: [Number] }) @ArrayNotEmpty() @ArrayMinSize(1) @ArrayMaxSize(3) - roleIds: number[]; + roleIds: number[] @ApiProperty({ description: '归属大区', type: Number }) @Type(() => Number) @IsInt() @IsOptional() - deptId?: number; + deptId?: number @ApiProperty({ description: '呢称', example: 'kz-admin' }) @IsOptional() @IsString() - nickname: string; + nickname: string @ApiProperty({ description: '邮箱', example: 'hi@kuizuo.cn' }) @IsUnique(UserEntity, { message: '邮箱已被注册' }) @IsEmail() @ValidateIf((o) => !isEmpty(o.email)) - email: string; + email: string @ApiProperty({ description: '手机号' }) @IsOptional() @IsString() - phone?: string; + phone?: string @ApiProperty({ description: 'QQ' }) @IsOptional() @@ -69,40 +69,40 @@ export class UserDto { @Matches(/^[1-9][0-9]{4,10}$/) @MinLength(5) @MaxLength(11) - qq?: string; + qq?: string @ApiProperty({ description: '备注' }) @IsOptional() @IsString() - remark?: string; + remark?: string @ApiProperty({ description: '状态' }) @IsIn([0, 1]) - status: number; + status: number } export class UserListDto extends PageOptionsDto { @ApiProperty({ description: '登录账号' }) @IsString() @IsOptional() - username: string; + username: string @ApiProperty({ description: '呢称' }) @IsOptional() - nickname: string; + nickname: string @ApiProperty({ description: '归属大区', example: 1 }) @IsInt() @IsOptional() - deptId: number; + deptId: number @ApiProperty({ description: '邮箱', example: 'hi@kuizuo.cn' }) @IsEmail() @IsOptional() - email: string; + email: string @ApiProperty({ description: '状态', example: 0 }) @IsInt() @IsOptional() - status: number; + status: number } diff --git a/apps/api/src/modules/user/entities/user.entity.ts b/apps/api/src/modules/user/entities/user.entity.ts index bd8ca57..bc36c59 100644 --- a/apps/api/src/modules/user/entities/user.entity.ts +++ b/apps/api/src/modules/user/entities/user.entity.ts @@ -1,4 +1,4 @@ -import { Exclude } from 'class-transformer'; +import { Exclude } from 'class-transformer' import { PrimaryGeneratedColumn, Column, @@ -8,61 +8,61 @@ import { OneToMany, ManyToOne, JoinColumn, -} from 'typeorm'; +} from 'typeorm' -import { AbstractEntity } from '@/common/entity/abstract.entity'; +import { AbstractEntity } from '@/common/entity/abstract.entity' -import { AccessTokenEntity } from '@/modules/auth/entities/access-token.entity'; +import { AccessTokenEntity } from '@/modules/auth/entities/access-token.entity' -import { DeptEntity } from '@/modules/system/dept/dept.entity'; -import { RoleEntity } from '@/modules/system/role/role.entity'; +import { DeptEntity } from '@/modules/system/dept/dept.entity' +import { RoleEntity } from '@/modules/system/role/role.entity' @Entity({ name: 'sys_user' }) export class UserEntity extends AbstractEntity { @PrimaryGeneratedColumn() - id: number; + id: number @Column({ unique: true }) - username: string; + username: string @Exclude() @Column() - password: string; + password: string @Column({ length: 32 }) - psalt: string; + psalt: string @Column({ name: 'nick_name', nullable: true }) - nickname: string; + nickname: string @Column({ name: 'avatar', nullable: true }) - avatar: string; + avatar: string @Column({ nullable: true }) - qq: string; + qq: string @Column({ nullable: true }) - email: string; + email: string @Column({ nullable: true }) - phone: string; + phone: string @Column({ nullable: true }) - remark: string; + remark: string @Column({ type: 'tinyint', nullable: true, default: 1 }) - status: number; + status: number @ManyToMany(() => RoleEntity, (role) => role.users) @JoinTable() - roles: RoleEntity[]; + roles: RoleEntity[] @ManyToOne(() => DeptEntity, (dept) => dept.users) @JoinColumn() - dept: DeptEntity; + dept: DeptEntity @OneToMany(() => AccessTokenEntity, (accessToken) => accessToken.user, { cascade: true, }) - accessTokens: AccessTokenEntity[]; + accessTokens: AccessTokenEntity[] } diff --git a/apps/api/src/modules/user/user.controller.ts b/apps/api/src/modules/user/user.controller.ts index 7e4d930..e9ba870 100644 --- a/apps/api/src/modules/user/user.controller.ts +++ b/apps/api/src/modules/user/user.controller.ts @@ -1,22 +1,14 @@ -import { - Body, - Controller, - Delete, - Get, - Post, - Put, - Query, -} from '@nestjs/common'; -import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { Body, Controller, Delete, Get, Post, Put, Query } from '@nestjs/common' +import { ApiOperation, ApiTags } from '@nestjs/swagger' -import { IdParam } from '@/common/decorators/id-param.decorator'; -import { ApiSecurityAuth } from '@/common/decorators/swagger.decorator'; -import { Permission } from '@/modules/auth/decorators/permission.decorator'; -import { MenuService } from '@/modules/system/menu/menu.service'; +import { IdParam } from '@/common/decorators/id-param.decorator' +import { ApiSecurityAuth } from '@/common/decorators/swagger.decorator' +import { Permission } from '@/modules/auth/decorators/permission.decorator' +import { MenuService } from '@/modules/system/menu/menu.service' -import { UserPasswordDto } from './dto/password.dto'; -import { UserDto, UserListDto } from './dto/user.dto'; -import { UserService } from './user.service'; +import { UserPasswordDto } from './dto/password.dto' +import { UserDto, UserListDto } from './dto/user.dto' +import { UserService } from './user.service' export const Permissions = { LIST: 'system:user:list', @@ -27,7 +19,7 @@ export const Permissions = { PASSWORD_UPDATE: 'system:user:password:update', PASSWORD_RESET: 'system:user:pass:reset', -} as const; +} as const @ApiTags('System - 用户模块') @ApiSecurityAuth() @@ -42,21 +34,21 @@ export class UserController { @ApiOperation({ summary: '获取用户列表' }) @Permission(Permissions.LIST) async list(@Query() dto: UserListDto) { - return this.userService.findAll(dto); + return this.userService.findAll(dto) } @Get(':id') @ApiOperation({ summary: '查询用户' }) @Permission(Permissions.READ) async read(@IdParam() id: number) { - return this.userService.info(id); + return this.userService.info(id) } @Post() @ApiOperation({ summary: '新增用户' }) @Permission(Permissions.CREATE) async create(@Body() dto: UserDto): Promise { - await this.userService.create(dto); + await this.userService.create(dto) } @Put(':id') @@ -66,22 +58,22 @@ export class UserController { @IdParam() id: number, @Body() dto: Partial, ): Promise { - await this.userService.update(id, dto); - await this.menuService.refreshPerms(id); + await this.userService.update(id, dto) + await this.menuService.refreshPerms(id) } @Delete(':id') @ApiOperation({ summary: '删除用户' }) @Permission(Permissions.DELETE) async delete(@IdParam() id: number): Promise { - await this.userService.delete([id]); - await this.userService.multiForbidden([id]); + await this.userService.delete([id]) + await this.userService.multiForbidden([id]) } @Post(':id/password') @ApiOperation({ summary: '更改用户密码' }) @Permission(Permissions.PASSWORD_UPDATE) async password(@Body() dto: UserPasswordDto): Promise { - await this.userService.forceUpdatePassword(dto.id, dto.password); + await this.userService.forceUpdatePassword(dto.id, dto.password) } } diff --git a/apps/api/src/modules/user/user.model.ts b/apps/api/src/modules/user/user.model.ts index beb082b..1493187 100644 --- a/apps/api/src/modules/user/user.model.ts +++ b/apps/api/src/modules/user/user.model.ts @@ -1,21 +1,21 @@ -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty } from '@nestjs/swagger' export class AccountInfo { @ApiProperty({ description: '用户名' }) - username: string; + username: string @ApiProperty({ description: '昵称' }) - nickname: string; + nickname: string @ApiProperty({ description: '邮箱' }) - email: string; + email: string @ApiProperty({ description: '手机号' }) - phone: string; + phone: string @ApiProperty({ description: '备注' }) - remark: string; + remark: string @ApiProperty({ description: '头像' }) - avatar: string; + avatar: string } diff --git a/apps/api/src/modules/user/user.module.ts b/apps/api/src/modules/user/user.module.ts index 338f5d5..960f269 100644 --- a/apps/api/src/modules/user/user.module.ts +++ b/apps/api/src/modules/user/user.module.ts @@ -1,16 +1,16 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; +import { Module } from '@nestjs/common' +import { TypeOrmModule } from '@nestjs/typeorm' -import { DictModule } from '../system/dict/dict.module'; +import { DictModule } from '../system/dict/dict.module' -import { MenuModule } from '../system/menu/menu.module'; -import { RoleModule } from '../system/role/role.module'; +import { MenuModule } from '../system/menu/menu.module' +import { RoleModule } from '../system/role/role.module' -import { UserEntity } from './entities/user.entity'; -import { UserController } from './user.controller'; -import { UserService } from './user.service'; +import { UserEntity } from './entities/user.entity' +import { UserController } from './user.controller' +import { UserService } from './user.service' -const providers = [UserService]; +const providers = [UserService] @Module({ imports: [ diff --git a/apps/api/src/modules/user/user.service.ts b/apps/api/src/modules/user/user.service.ts index 0bba5f1..af9e4cb 100644 --- a/apps/api/src/modules/user/user.service.ts +++ b/apps/api/src/modules/user/user.service.ts @@ -1,34 +1,34 @@ -import { InjectRedis } from '@liaoliaots/nestjs-redis'; -import { BadRequestException, Injectable } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm'; -import Redis from 'ioredis'; -import { isEmpty, isNil } from 'lodash'; +import { InjectRedis } from '@liaoliaots/nestjs-redis' +import { BadRequestException, Injectable } from '@nestjs/common' +import { ConfigService } from '@nestjs/config' +import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm' +import Redis from 'ioredis' +import { isEmpty, isNil } from 'lodash' -import { EntityManager, Like, Repository } from 'typeorm'; +import { EntityManager, Like, Repository } from 'typeorm' -import { BusinessException } from '@/common/exceptions/biz.exception'; -import { IAppConfig } from '@/config'; -import { ErrorEnum } from '@/constants/error-code.constant'; -import { SYS_USER_INITPASSWORD } from '@/constants/system.constant'; +import { BusinessException } from '@/common/exceptions/biz.exception' +import { IAppConfig } from '@/config' +import { ErrorEnum } from '@/constants/error-code.constant' +import { SYS_USER_INITPASSWORD } from '@/constants/system.constant' -import { paginate } from '@/helper/paginate'; -import { Pagination } from '@/helper/paginate/pagination'; -import { AccountUpdateDto } from '@/modules/auth/dto/account.dto'; -import { RegisterDto } from '@/modules/auth/dto/auth.dto'; -import { QQService } from '@/shared/qq/qq.service'; +import { paginate } from '@/helper/paginate' +import { Pagination } from '@/helper/paginate/pagination' +import { AccountUpdateDto } from '@/modules/auth/dto/account.dto' +import { RegisterDto } from '@/modules/auth/dto/auth.dto' +import { QQService } from '@/shared/qq/qq.service' -import { MD5, randomValue } from '@/utils'; +import { MD5, randomValue } from '@/utils' -import { DictService } from '../system/dict/dict.service'; +import { DictService } from '../system/dict/dict.service' -import { RoleEntity } from '../system/role/role.entity'; +import { RoleEntity } from '../system/role/role.entity' -import { UserStatus } from './constant'; -import { PasswordUpdateDto } from './dto/password.dto'; -import { UserDto, UserListDto } from './dto/user.dto'; -import { UserEntity } from './entities/user.entity'; -import { AccountInfo } from './user.model'; +import { UserStatus } from './constant' +import { PasswordUpdateDto } from './dto/password.dto' +import { UserDto, UserListDto } from './dto/user.dto' +import { UserEntity } from './entities/user.entity' +import { AccountInfo } from './user.model' @Injectable() export class UserService { @@ -52,7 +52,7 @@ export class UserService { id, status: UserStatus.Enabled, }) - .getOne(); + .getOne() } async findUserByUserName(username: string): Promise { @@ -62,7 +62,7 @@ export class UserService { username, status: UserStatus.Enabled, }) - .getOne(); + .getOne() } /** @@ -74,21 +74,21 @@ export class UserService { .createQueryBuilder('user') .leftJoinAndSelect('user.roles', 'role') .where(`user.id = :uid`, { uid }) - .getOne(); + .getOne() - if (isEmpty(user)) throw new BusinessException(ErrorEnum.USER_NOT_FOUND); + if (isEmpty(user)) throw new BusinessException(ErrorEnum.USER_NOT_FOUND) - delete user?.psalt; + delete user?.psalt - return user; + return user } /** * 更新个人信息 */ async updateAccountInfo(uid: number, info: AccountUpdateDto): Promise { - const user = await this.userRepository.findOneBy({ id: uid }); - if (isEmpty(user)) throw new BusinessException(ErrorEnum.USER_NOT_FOUND); + const user = await this.userRepository.findOneBy({ id: uid }) + if (isEmpty(user)) throw new BusinessException(ErrorEnum.USER_NOT_FOUND) const data = { ...(info.nickname ? { nickname: info.nickname } : null), @@ -97,45 +97,45 @@ export class UserService { ...(info.phone ? { phone: info.phone } : null), ...(info.qq ? { qq: info.qq } : null), ...(info.remark ? { remark: info.remark } : null), - }; + } if (!info.avatar && info.qq) { // 如果qq不等于原qq,则更新qq头像 if (info.qq !== user.qq) { - data.avatar = await this.qqService.getAvater(info.qq); + data.avatar = await this.qqService.getAvater(info.qq) } } - await this.userRepository.update(uid, data); + await this.userRepository.update(uid, data) } /** * 更改密码 */ async updatePassword(uid: number, dto: PasswordUpdateDto): Promise { - const user = await this.userRepository.findOneBy({ id: uid }); + const user = await this.userRepository.findOneBy({ id: uid }) if (isEmpty(user)) { - throw new BusinessException(ErrorEnum.USER_NOT_FOUND); + throw new BusinessException(ErrorEnum.USER_NOT_FOUND) } - const comparePassword = MD5(`${dto.oldPassword}${user.psalt}`); + const comparePassword = MD5(`${dto.oldPassword}${user.psalt}`) // 原密码不一致,不允许更改 if (user.password !== comparePassword) { - throw new BusinessException(ErrorEnum.PASSWORD_MISMATCH); + throw new BusinessException(ErrorEnum.PASSWORD_MISMATCH) } - const password = MD5(`${dto.newPassword}${user.psalt}`); - await this.userRepository.update({ id: uid }, { password }); - await this.upgradePasswordV(user.id); + const password = MD5(`${dto.newPassword}${user.psalt}`) + await this.userRepository.update({ id: uid }, { password }) + await this.upgradePasswordV(user.id) } /** * 直接更改密码 */ async forceUpdatePassword(uid: number, password: string): Promise { - const user = await this.userRepository.findOneBy({ id: uid }); + const user = await this.userRepository.findOneBy({ id: uid }) - const newPassword = MD5(`${password}${user.psalt}`); - await this.userRepository.update({ id: uid }, { password: newPassword }); - await this.upgradePasswordV(user.id); + const newPassword = MD5(`${password}${user.psalt}`) + await this.userRepository.update({ id: uid }, { password: newPassword }) + await this.upgradePasswordV(user.id) } /** @@ -145,27 +145,25 @@ export class UserService { username, password, roleIds: roles, - deptId, ...data }: UserDto): Promise { const exists = await this.userRepository.findOneBy({ username, - }); + }) if (!isEmpty(exists)) { - throw new BusinessException(ErrorEnum.SYSTEM_USER_EXISTS); + throw new BusinessException(ErrorEnum.SYSTEM_USER_EXISTS) } await this.entityManager.transaction(async (manager) => { - const salt = randomValue(32); + const salt = randomValue(32) - let password; if (!password) { const initPassword = await this.dictService.findValueByKey( SYS_USER_INITPASSWORD, - ); - password = MD5(`${initPassword ?? '123456'}${salt}`); + ) + password = MD5(`${initPassword ?? '123456'}${salt}`) } else { - password = MD5(`${password ?? '123456'}${salt}`); + password = MD5(`${password ?? '123456'}${salt}`) } const u = manager.create(UserEntity, { @@ -174,11 +172,11 @@ export class UserService { ...data, psalt: salt, roles: await this.roleRepository.findByIds(roles), - }); + }) - const result = await manager.save(u); - return result; - }); + const result = await manager.save(u) + return result + }) } /** @@ -190,38 +188,38 @@ export class UserService { ): Promise { await this.entityManager.transaction(async (manager) => { if (password) { - await this.forceUpdatePassword(id, password); + await this.forceUpdatePassword(id, password) } await manager.update(UserEntity, id, { ...data, status, - }); + }) const user = await this.userRepository .createQueryBuilder('user') .leftJoinAndSelect('user.roles', 'roles') .leftJoinAndSelect('user.dept', 'dept') .where('user.id = :id', { id }) - .getOne(); + .getOne() await manager .createQueryBuilder() .relation(UserEntity, 'roles') .of(id) - .addAndRemove(roleIds, user.roles); + .addAndRemove(roleIds, user.roles) await manager .createQueryBuilder() .relation(UserEntity, 'dept') .of(id) - .set(deptId); + .set(deptId) if (status === 0) { // 禁用状态 - await this.forbidden(id); + await this.forbidden(id) } - }); + }) } /** @@ -234,24 +232,24 @@ export class UserService { .leftJoinAndSelect('user.roles', 'roles') .leftJoinAndSelect('user.dept', 'dept') .where('user.id = :id', { id }) - .getOne(); + .getOne() - delete user.password; - delete user.psalt; + delete user.password + delete user.psalt - return user; + return user } /** * 根据ID列表删除用户 */ async delete(userIds: number[]): Promise { - const rootUserId = await this.findRootUserId(); + const rootUserId = await this.findRootUserId() if (userIds.includes(rootUserId)) { - throw new BadRequestException('不能删除root用户!'); + throw new BadRequestException('不能删除root用户!') } - await this.userRepository.delete(userIds); - await this.userRepository.delete(userIds); + await this.userRepository.delete(userIds) + await this.userRepository.delete(userIds) } /** @@ -260,8 +258,8 @@ export class UserService { async findRootUserId(): Promise { const user = await this.userRepository.findOneBy({ roles: { id: this.configService.get('app').adminRoleId }, - }); - return user.id; + }) + return user.id } /** @@ -286,25 +284,25 @@ export class UserService { ...(nickname ? { nickname: Like(`%${nickname}%`) } : null), ...(email ? { email: Like(`%${email}%`) } : null), ...(status ? { status } : null), - }); + }) if (deptId) { - queryBuilder.andWhere('dept.id = :deptId', { deptId }); + queryBuilder.andWhere('dept.id = :deptId', { deptId }) } return paginate(queryBuilder, { page, pageSize, - }); + }) } /** * 禁用用户 */ async forbidden(uid: number): Promise { - await this.redis.del(`admin:passwordVersion:${uid}`); - await this.redis.del(`admin:token:${uid}`); - await this.redis.del(`admin:perms:${uid}`); + await this.redis.del(`admin:passwordVersion:${uid}`) + await this.redis.del(`admin:token:${uid}`) + await this.redis.del(`admin:perms:${uid}`) } /** @@ -312,17 +310,17 @@ export class UserService { */ async multiForbidden(uids: number[]): Promise { if (uids) { - const pvs: string[] = []; - const ts: string[] = []; - const ps: string[] = []; + const pvs: string[] = [] + const ts: string[] = [] + const ps: string[] = [] uids.forEach((e) => { - pvs.push(`admin:passwordVersion:${e}`); - ts.push(`admin:token:${e}`); - ps.push(`admin:perms:${e}`); - }); - await this.redis.del(pvs); - await this.redis.del(ts); - await this.redis.del(ps); + pvs.push(`admin:passwordVersion:${e}`) + ts.push(`admin:token:${e}`) + ps.push(`admin:perms:${e}`) + }) + await this.redis.del(pvs) + await this.redis.del(ts) + await this.redis.del(ps) } } @@ -331,9 +329,9 @@ export class UserService { */ async upgradePasswordV(id: number): Promise { // admin:passwordVersion:${param.id} - const v = await this.redis.get(`admin:passwordVersion:${id}`); + const v = await this.redis.get(`admin:passwordVersion:${id}`) if (!isEmpty(v)) { - await this.redis.set(`admin:passwordVersion:${id}`, parseInt(v) + 1); + await this.redis.set(`admin:passwordVersion:${id}`, parseInt(v) + 1) } } @@ -341,11 +339,11 @@ export class UserService { * 判断用户名是否存在 */ async exist(username: string) { - const user = await this.userRepository.findOneBy({ username }); + const user = await this.userRepository.findOneBy({ username }) if (isNil(user)) { - throw new BusinessException(ErrorEnum.SYSTEM_USER_EXISTS); + throw new BusinessException(ErrorEnum.SYSTEM_USER_EXISTS) } - return true; + return true } /** @@ -354,25 +352,25 @@ export class UserService { async register({ username, ...data }: RegisterDto): Promise { const exists = await this.userRepository.findOneBy({ username, - }); + }) if (!isEmpty(exists)) - throw new BusinessException(ErrorEnum.SYSTEM_USER_EXISTS); + throw new BusinessException(ErrorEnum.SYSTEM_USER_EXISTS) await this.entityManager.transaction(async (manager) => { - const salt = randomValue(32); + const salt = randomValue(32) - const password = MD5(`${data.password ?? 'a123456'}${salt}`); + const password = MD5(`${data.password ?? 'a123456'}${salt}`) const u = manager.create(UserEntity, { username, password, status: 1, psalt: salt, - }); + }) - const user = await manager.save(u); + const user = await manager.save(u) - return user; - }); + return user + }) } } diff --git a/apps/api/src/setup-swagger.ts b/apps/api/src/setup-swagger.ts index 902c851..639adff 100644 --- a/apps/api/src/setup-swagger.ts +++ b/apps/api/src/setup-swagger.ts @@ -1,26 +1,26 @@ -import { INestApplication, Logger } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; +import { INestApplication, Logger } from '@nestjs/common' +import { ConfigService } from '@nestjs/config' +import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger' -import { API_SECURITY_AUTH } from './common/decorators/swagger.decorator'; -import { AbstractEntity } from './common/entity/abstract.entity'; -import { ResOp, TreeResult } from './common/model/response.model'; -import { IAppConfig, ISwaggerConfig } from './config'; -import { Pagination } from './helper/paginate/pagination'; +import { API_SECURITY_AUTH } from './common/decorators/swagger.decorator' +import { AbstractEntity } from './common/entity/abstract.entity' +import { ResOp, TreeResult } from './common/model/response.model' +import { IAppConfig, ISwaggerConfig } from './config' +import { Pagination } from './helper/paginate/pagination' export function setupSwagger( app: INestApplication, configService: ConfigService, ): void { - const { name, port } = configService.get('app'); - const { enable, path } = configService.get('swagger'); + const { name, port } = configService.get('app')! + const { enable, path } = configService.get('swagger')! - if (!enable) return; + if (!enable) return const documentBuilder = new DocumentBuilder() .setTitle(name) .setDescription(`${name} API document`) - .setVersion('1.0'); + .setVersion('1.0') // auth security documentBuilder.addSecurity(API_SECURITY_AUTH, { @@ -28,16 +28,16 @@ export function setupSwagger( type: 'apiKey', in: 'header', name: 'Authorization', - }); + }) const document = SwaggerModule.createDocument(app, documentBuilder.build(), { ignoreGlobalPrefix: false, extraModels: [AbstractEntity, ResOp, Pagination, TreeResult], - }); + }) - SwaggerModule.setup(path, app, document); + SwaggerModule.setup(path, app, document) // started log - const logger = new Logger('SwaggerModule'); - logger.log(`Document running on http://127.0.0.1:${port}/${path}`); + const logger = new Logger('SwaggerModule') + logger.log(`Document running on http://127.0.0.1:${port}/${path}`) } diff --git a/apps/api/src/shared/database/constraints/entity-exist.constraint.ts b/apps/api/src/shared/database/constraints/entity-exist.constraint.ts index 987d72a..d5fc3b9 100644 --- a/apps/api/src/shared/database/constraints/entity-exist.constraint.ts +++ b/apps/api/src/shared/database/constraints/entity-exist.constraint.ts @@ -1,18 +1,18 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common' import { registerDecorator, ValidationArguments, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface, -} from 'class-validator'; -import { DataSource, ObjectType, Repository } from 'typeorm'; +} from 'class-validator' +import { DataSource, ObjectType, Repository } from 'typeorm' type Condition = { - entity: ObjectType; + entity: ObjectType // 如果没有指定字段则使用当前验证的属性作为查询依据 - field?: string; -}; + field?: string +} /** * 查询某个字段的值是否在数据表中存在 @@ -23,30 +23,30 @@ export class EntityExistConstraint implements ValidatorConstraintInterface { constructor(private dataSource: DataSource) {} async validate(value: string, args: ValidationArguments) { - let repo: Repository; + let repo: Repository - if (!value) return true; + if (!value) return true // 默认对比字段是id - let field = 'id'; + let field = 'id' // 通过传入的 entity 获取其 repository if ('entity' in args.constraints[0]) { // 传入的是对象 可以指定对比字段 - field = args.constraints[0].field ?? 'id'; - repo = this.dataSource.getRepository(args.constraints[0].entity); + field = args.constraints[0].field ?? 'id' + repo = this.dataSource.getRepository(args.constraints[0].entity) } else { // 传入的是实体类 - repo = this.dataSource.getRepository(args.constraints[0]); + repo = this.dataSource.getRepository(args.constraints[0]) } // 通过查询记录是否存在进行验证 - const item = await repo.findOne({ where: { [field]: value } }); - return !!item; + const item = await repo.findOne({ where: { [field]: value } }) + return !!item } defaultMessage(args: ValidationArguments) { if (!args.constraints[0]) { - return 'Model not been specified!'; + return 'Model not been specified!' } - return `All instance of ${args.constraints[0].name} must been exists in databse!`; + return `All instance of ${args.constraints[0].name} must been exists in databse!` } } @@ -58,12 +58,12 @@ export class EntityExistConstraint implements ValidatorConstraintInterface { function IsEntityExist( entity: ObjectType, validationOptions?: ValidationOptions, -): (object: Record, propertyName: string) => void; +): (object: Record, propertyName: string) => void function IsEntityExist( condition: { entity: ObjectType; field?: string }, validationOptions?: ValidationOptions, -): (object: Record, propertyName: string) => void; +): (object: Record, propertyName: string) => void function IsEntityExist( condition: ObjectType | { entity: ObjectType; field?: string }, @@ -76,8 +76,8 @@ function IsEntityExist( options: validationOptions, constraints: [condition], validator: EntityExistConstraint, - }); - }; + }) + } } -export { IsEntityExist }; +export { IsEntityExist } diff --git a/apps/api/src/shared/database/constraints/unique.constraint.ts b/apps/api/src/shared/database/constraints/unique.constraint.ts index 3714cee..90d7fb2 100644 --- a/apps/api/src/shared/database/constraints/unique.constraint.ts +++ b/apps/api/src/shared/database/constraints/unique.constraint.ts @@ -1,19 +1,19 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common' import { registerDecorator, ValidationArguments, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface, -} from 'class-validator'; -import { isNil, merge } from 'lodash'; -import { DataSource, ObjectType } from 'typeorm'; +} from 'class-validator' +import { isNil, merge } from 'lodash' +import { DataSource, ObjectType } from 'typeorm' type Condition = { - entity: ObjectType; + entity: ObjectType // 如果没有指定字段则使用当前验证的属性作为查询依据 - field?: string; -}; + field?: string +} /** * 验证某个字段的唯一性 @@ -27,38 +27,38 @@ export class UniqueConstraint implements ValidatorConstraintInterface { // 获取要验证的模型和字段 const config: Omit = { field: args.property, - }; + } const condition = ('entity' in args.constraints[0] ? merge(config, args.constraints[0]) : { ...config, entity: args.constraints[0], - }) as unknown as Required; - if (!condition.entity) return false; + }) as unknown as Required + if (!condition.entity) return false try { // 查询是否存在数据,如果已经存在则验证失败 - const repo = this.dataSource.getRepository(condition.entity); + const repo = this.dataSource.getRepository(condition.entity) return isNil( await repo.findOne({ where: { [condition.field]: value }, }), - ); + ) } catch (err) { // 如果数据库操作异常则验证失败 - return false; + return false } } defaultMessage(args: ValidationArguments) { - const { entity, property } = args.constraints[0]; - const queryProperty = property ?? args.property; + const { entity, property } = args.constraints[0] + const queryProperty = property ?? args.property if (!(args.object as any).getManager) { - return 'getManager function not been found!'; + return 'getManager function not been found!' } if (!entity) { - return 'Model not been specified!'; + return 'Model not been specified!' } - return `${queryProperty} of ${entity.name} must been unique!`; + return `${queryProperty} of ${entity.name} must been unique!` } } @@ -70,12 +70,12 @@ export class UniqueConstraint implements ValidatorConstraintInterface { function IsUnique( entity: ObjectType, validationOptions?: ValidationOptions, -): (object: Record, propertyName: string) => void; +): (object: Record, propertyName: string) => void function IsUnique( condition: Condition, validationOptions?: ValidationOptions, -): (object: Record, propertyName: string) => void; +): (object: Record, propertyName: string) => void function IsUnique( params: ObjectType | Condition, @@ -88,8 +88,8 @@ function IsUnique( options: validationOptions, constraints: [params], validator: UniqueConstraint, - }); - }; + }) + } } -export { IsUnique }; +export { IsUnique } diff --git a/apps/api/src/shared/database/database.module.ts b/apps/api/src/shared/database/database.module.ts index c24527c..9ccaf45 100644 --- a/apps/api/src/shared/database/database.module.ts +++ b/apps/api/src/shared/database/database.module.ts @@ -1,28 +1,28 @@ -import { Module } from '@nestjs/common'; +import { Module } from '@nestjs/common' -import { ConfigService } from '@nestjs/config'; -import { TypeOrmModule } from '@nestjs/typeorm'; +import { ConfigService } from '@nestjs/config' +import { TypeOrmModule } from '@nestjs/typeorm' -import { LoggerOptions } from 'typeorm'; +import { LoggerOptions } from 'typeorm' -import { IDatabaseConfig } from '@/config'; -import { env } from '@/global/env'; +import { IDatabaseConfig } from '@/config' +import { env } from '@/global/env' -import { EntityExistConstraint } from './constraints/entity-exist.constraint'; -import { UniqueConstraint } from './constraints/unique.constraint'; -import { TypeORMLogger } from './typeorm-logger'; +import { EntityExistConstraint } from './constraints/entity-exist.constraint' +import { UniqueConstraint } from './constraints/unique.constraint' +import { TypeORMLogger } from './typeorm-logger' -const providers = [EntityExistConstraint, UniqueConstraint]; +const providers = [EntityExistConstraint, UniqueConstraint] @Module({ imports: [ TypeOrmModule.forRootAsync({ useFactory: (configService: ConfigService) => { - let loggerOptions: LoggerOptions = env('DB_LOGGING') as 'all'; + let loggerOptions: LoggerOptions = env('DB_LOGGING') as 'all' try { // 解析成 js 数组 ['error'] - loggerOptions = JSON.parse(loggerOptions); + loggerOptions = JSON.parse(loggerOptions) } catch { // ignore } @@ -32,7 +32,7 @@ const providers = [EntityExistConstraint, UniqueConstraint]; autoLoadEntities: true, logging: loggerOptions, logger: new TypeORMLogger(loggerOptions), - }; + } }, inject: [ConfigService], }), diff --git a/apps/api/src/shared/database/typeorm-logger.ts b/apps/api/src/shared/database/typeorm-logger.ts index 5b65822..840ee2a 100644 --- a/apps/api/src/shared/database/typeorm-logger.ts +++ b/apps/api/src/shared/database/typeorm-logger.ts @@ -1,21 +1,21 @@ -import { Logger } from '@nestjs/common'; -import { Logger as ITypeORMLogger, LoggerOptions, QueryRunner } from 'typeorm'; +import { Logger } from '@nestjs/common' +import { Logger as ITypeORMLogger, LoggerOptions, QueryRunner } from 'typeorm' export class TypeORMLogger implements ITypeORMLogger { - private logger = new Logger(TypeORMLogger.name); + private logger = new Logger(TypeORMLogger.name) constructor(private options: LoggerOptions) {} logQuery(query: string, parameters?: any[], _queryRunner?: QueryRunner) { - if (!this.isEnable('query')) return; + if (!this.isEnable('query')) return const sql = query + (parameters && parameters.length ? ` -- PARAMETERS: ${this.stringifyParams(parameters)}` - : ''); + : '') - this.logger.log(`[QUERY]: ${sql}`); + this.logger.log(`[QUERY]: ${sql}`) } logQueryError( @@ -24,15 +24,15 @@ export class TypeORMLogger implements ITypeORMLogger { parameters?: any[], _queryRunner?: QueryRunner, ) { - if (!this.isEnable('error')) return; + if (!this.isEnable('error')) return const sql = query + (parameters && parameters.length ? ` -- PARAMETERS: ${this.stringifyParams(parameters)}` - : ''); + : '') - this.logger.error([`[FAILED QUERY]: ${sql}`, `[QUERY ERROR]: ${error}`]); + this.logger.error([`[FAILED QUERY]: ${sql}`, `[QUERY ERROR]: ${error}`]) } logQuerySlow( @@ -45,21 +45,21 @@ export class TypeORMLogger implements ITypeORMLogger { query + (parameters && parameters.length ? ` -- PARAMETERS: ${this.stringifyParams(parameters)}` - : ''); + : '') - this.logger.warn(`[SLOW QUERY: ${time} ms]: ${sql}`); + this.logger.warn(`[SLOW QUERY: ${time} ms]: ${sql}`) } logSchemaBuild(message: string, _queryRunner?: QueryRunner) { - if (!this.isEnable('schema')) return; + if (!this.isEnable('schema')) return - this.logger.log(message); + this.logger.log(message) } logMigration(message: string, _queryRunner?: QueryRunner) { - if (!this.isEnable('migration')) return; + if (!this.isEnable('migration')) return - this.logger.log(message); + this.logger.log(message) } log( @@ -67,20 +67,20 @@ export class TypeORMLogger implements ITypeORMLogger { message: any, _queryRunner?: QueryRunner, ) { - if (!this.isEnable(level)) return; + if (!this.isEnable(level)) return switch (level) { case 'log': - this.logger.debug(message); - break; + this.logger.debug(message) + break case 'info': - this.logger.log(message); - break; + this.logger.log(message) + break case 'warn': - this.logger.warn(message); - break; + this.logger.warn(message) + break default: - break; + break } } @@ -90,10 +90,10 @@ export class TypeORMLogger implements ITypeORMLogger { */ private stringifyParams(parameters: any[]) { try { - return JSON.stringify(parameters); + return JSON.stringify(parameters) } catch (error) { // most probably circular objects in parameters - return parameters; + return parameters } } @@ -107,6 +107,6 @@ export class TypeORMLogger implements ITypeORMLogger { this.options === 'all' || this.options === true || (Array.isArray(this.options) && this.options.indexOf(level) !== -1) - ); + ) } } diff --git a/apps/api/src/shared/ip/ip.service.ts b/apps/api/src/shared/ip/ip.service.ts index acb7385..28fce35 100644 --- a/apps/api/src/shared/ip/ip.service.ts +++ b/apps/api/src/shared/ip/ip.service.ts @@ -1,5 +1,5 @@ -import { HttpService } from '@nestjs/axios'; -import { Injectable } from '@nestjs/common'; +import { HttpService } from '@nestjs/axios' +import { Injectable } from '@nestjs/common' @Injectable() export class IpService { @@ -8,7 +8,7 @@ export class IpService { async getAddress(ip: string) { const { data } = await this.http.axiosRef.get( `https://api.kuizuo.cn/api/ip-location?ip=${ip}&type=json`, - ); - return data.addr; + ) + return data.addr } } diff --git a/apps/api/src/shared/logger/logger.module.ts b/apps/api/src/shared/logger/logger.module.ts index 664d4fc..cba9cfb 100644 --- a/apps/api/src/shared/logger/logger.module.ts +++ b/apps/api/src/shared/logger/logger.module.ts @@ -1,6 +1,6 @@ -import { Module } from '@nestjs/common'; +import { Module } from '@nestjs/common' -import { MyLogger } from './logger.service'; +import { MyLogger } from './logger.service' @Module({ providers: [MyLogger], diff --git a/apps/api/src/shared/logger/logger.service.ts b/apps/api/src/shared/logger/logger.service.ts index e1f2d6a..cb6e7ee 100644 --- a/apps/api/src/shared/logger/logger.service.ts +++ b/apps/api/src/shared/logger/logger.service.ts @@ -1,15 +1,11 @@ -import { - ConsoleLogger, - ConsoleLoggerOptions, - Injectable, -} from '@nestjs/common'; +import { ConsoleLogger, ConsoleLoggerOptions, Injectable } from '@nestjs/common' -import { ConfigService } from '@nestjs/config'; -import type { Logger as WinstonLogger } from 'winston'; +import { ConfigService } from '@nestjs/config' +import type { Logger as WinstonLogger } from 'winston' -import { createLogger, format, transports, config } from 'winston'; +import { createLogger, format, transports, config } from 'winston' -import 'winston-daily-rotate-file'; +import 'winston-daily-rotate-file' export enum LogLevel { ERROR = 'error', @@ -21,23 +17,23 @@ export enum LogLevel { @Injectable() export class MyLogger extends ConsoleLogger { - private winstonLogger: WinstonLogger; + private winstonLogger: WinstonLogger constructor( context: string, options: ConsoleLoggerOptions, private configService: ConfigService, ) { - super(context, options); - this.initWinston(); + super(context, options) + this.initWinston() } protected get level(): LogLevel { - return this.configService.get('app.logger.level') as LogLevel; + return this.configService.get('app.logger.level') as LogLevel } protected get maxFiles(): number { - return this.configService.get('app.logger.maxFiles'); + return this.configService.get('app.logger.maxFiles') } protected initWinston(): void { @@ -66,7 +62,7 @@ export class MyLogger extends ConsoleLogger { auditFile: 'logs/.audit/app-error.json', }), ], - }); + }) // if (isDev) { // this.winstonLogger.add( @@ -82,36 +78,36 @@ export class MyLogger extends ConsoleLogger { } verbose(message: any, context?: string): void { - super.verbose.apply(this, [message, context]); + super.verbose.apply(this, [message, context]) - this.winstonLogger.log(LogLevel.VERBOSE, message, { context }); + this.winstonLogger.log(LogLevel.VERBOSE, message, { context }) } debug(message: any, context?: string): void { - super.debug.apply(this, [message, context]); + super.debug.apply(this, [message, context]) - this.winstonLogger.log(LogLevel.DEBUG, message, { context }); + this.winstonLogger.log(LogLevel.DEBUG, message, { context }) } log(message: any, context?: string): void { - super.log.apply(this, [message, context]); + super.log.apply(this, [message, context]) - this.winstonLogger.log(LogLevel.INFO, message, { context }); + this.winstonLogger.log(LogLevel.INFO, message, { context }) } warn(message: any, context?: string): void { - super.warn.apply(this, [message, context]); + super.warn.apply(this, [message, context]) - this.winstonLogger.log(LogLevel.WARN, message); + this.winstonLogger.log(LogLevel.WARN, message) } error(message: any, stack?: string, context?: string): void { - super.error.apply(this, [message, stack, context]); + super.error.apply(this, [message, stack, context]) - const hasStack = !!context; + const hasStack = !!context this.winstonLogger.log(LogLevel.ERROR, { context: hasStack ? context : stack, message: hasStack ? new Error(message) : message, - }); + }) } } diff --git a/apps/api/src/shared/mailer/mailer.service.ts b/apps/api/src/shared/mailer/mailer.service.ts index 430b8aa..0826de0 100644 --- a/apps/api/src/shared/mailer/mailer.service.ts +++ b/apps/api/src/shared/mailer/mailer.service.ts @@ -1,17 +1,17 @@ -import { InjectRedis } from '@liaoliaots/nestjs-redis'; -import { Injectable } from '@nestjs/common'; +import { InjectRedis } from '@liaoliaots/nestjs-redis' +import { Injectable } from '@nestjs/common' -import { ConfigService } from '@nestjs/config'; -import { MailerService as NestMailerService } from '@nestjs-modules/mailer'; -import dayjs from 'dayjs'; +import { ConfigService } from '@nestjs/config' +import { MailerService as NestMailerService } from '@nestjs-modules/mailer' +import dayjs from 'dayjs' -import Redis from 'ioredis'; +import Redis from 'ioredis' -import { BusinessException } from '@/common/exceptions/biz.exception'; -import { IAppConfig } from '@/config'; -import { ErrorEnum } from '@/constants/error-code.constant'; +import { BusinessException } from '@/common/exceptions/biz.exception' +import { IAppConfig } from '@/config' +import { ErrorEnum } from '@/constants/error-code.constant' -import { randomValue } from '@/utils'; +import { randomValue } from '@/utils' @Injectable() export class MailerService { @@ -22,7 +22,7 @@ export class MailerService { ) {} async send(to, subject, content): Promise { - console.log(to, subject, content); + console.log(to, subject, content) return this.mailerService.sendMail({ to, @@ -32,7 +32,7 @@ export class MailerService { }, subject, text: content, - }); + }) } async sendMailHtml(to, subject, html): Promise { @@ -44,93 +44,93 @@ export class MailerService { }, subject, html, - }); + }) } async sendCode(to, code = randomValue(4, '1234567890')) { - const content = `尊敬的用户您好,您的验证码是${code},请于5分钟内输入。`; + const content = `尊敬的用户您好,您的验证码是${code},请于5分钟内输入。` try { await this.send( to, `${this.configService.get('app').name}验证码通知`, content, - ); + ) } catch (error) { - console.log(error); - throw new BusinessException(ErrorEnum.VERIFICATION_CODE_SEND_FAILED); + console.log(error) + throw new BusinessException(ErrorEnum.VERIFICATION_CODE_SEND_FAILED) } return { to, code, - }; + } } async checkCode(to, code) { - const ret = await this.redis.get(`captcha:${to}`); + const ret = await this.redis.get(`captcha:${to}`) if (ret !== code) { - throw new BusinessException(ErrorEnum.INVALID_VERIFICATION_CODE); + throw new BusinessException(ErrorEnum.INVALID_VERIFICATION_CODE) } - await this.redis.del(`captcha:${to}`); + await this.redis.del(`captcha:${to}`) } async checkLimit(to, ip) { - const LIMIT_TIME = 5; + const LIMIT_TIME = 5 // ip限制 - const ipLimit = await this.redis.get(`ip:${ip}:send:limit`); - if (ipLimit) throw new BusinessException(ErrorEnum.TOO_MANY_REQUESTS); + const ipLimit = await this.redis.get(`ip:${ip}:send:limit`) + if (ipLimit) throw new BusinessException(ErrorEnum.TOO_MANY_REQUESTS) // 1分钟最多接收1条 - const limit = await this.redis.get(`captcha:${to}:limit`); - if (limit) throw new BusinessException(ErrorEnum.TOO_MANY_REQUESTS); + const limit = await this.redis.get(`captcha:${to}:limit`) + if (limit) throw new BusinessException(ErrorEnum.TOO_MANY_REQUESTS) // 1天一个邮箱最多接收5条 let limitCountOfDay: string | number = await this.redis.get( `captcha:${to}:limit-day`, - ); - limitCountOfDay = limitCountOfDay ? Number(limitCountOfDay) : 0; + ) + limitCountOfDay = limitCountOfDay ? Number(limitCountOfDay) : 0 if (limitCountOfDay > LIMIT_TIME) throw new BusinessException( ErrorEnum.MAXIMUM_FIVE_VERIFICATION_CODES_PER_DAY, - ); + ) // 1天一个ip最多发送5条 let ipLimitCountOfDay: string | number = await this.redis.get( `ip:${ip}:send:limit-day`, - ); - ipLimitCountOfDay = ipLimitCountOfDay ? Number(ipLimitCountOfDay) : 0; + ) + ipLimitCountOfDay = ipLimitCountOfDay ? Number(ipLimitCountOfDay) : 0 if (ipLimitCountOfDay > LIMIT_TIME) throw new BusinessException( ErrorEnum.MAXIMUM_FIVE_VERIFICATION_CODES_PER_DAY, - ); + ) } async log(to: string, code: string, ip: string) { const getRemainTime = () => { - const now = dayjs(); - return now.endOf('day').diff(now, 'second'); - }; + const now = dayjs() + return now.endOf('day').diff(now, 'second') + } - await this.redis.set(`captcha:${to}`, code, 'EX', 60 * 5); + await this.redis.set(`captcha:${to}`, code, 'EX', 60 * 5) - const limitCountOfDay = await this.redis.get(`captcha:${to}:limit-day`); - const ipLimitCountOfDay = await this.redis.get(`ip:${ip}:send:limit-day`); + const limitCountOfDay = await this.redis.get(`captcha:${to}:limit-day`) + const ipLimitCountOfDay = await this.redis.get(`ip:${ip}:send:limit-day`) - await this.redis.set(`ip:${ip}:send:limit`, 1, 'EX', 60); - await this.redis.set(`captcha:${to}:limit`, 1, 'EX', 60); + await this.redis.set(`ip:${ip}:send:limit`, 1, 'EX', 60) + await this.redis.set(`captcha:${to}:limit`, 1, 'EX', 60) await this.redis.set( `captcha:${to}:send:limit-count-day`, limitCountOfDay, 'EX', getRemainTime(), - ); + ) await this.redis.set( `ip:${ip}:send:limit-count-day`, ipLimitCountOfDay, 'EX', getRemainTime(), - ); + ) } } diff --git a/apps/api/src/shared/qq/qq.service.ts b/apps/api/src/shared/qq/qq.service.ts index e9b4d8c..82e5a76 100644 --- a/apps/api/src/shared/qq/qq.service.ts +++ b/apps/api/src/shared/qq/qq.service.ts @@ -1,5 +1,5 @@ -import { HttpService } from '@nestjs/axios'; -import { Injectable } from '@nestjs/common'; +import { HttpService } from '@nestjs/axios' +import { Injectable } from '@nestjs/common' @Injectable() export class QQService { @@ -8,11 +8,11 @@ export class QQService { async getNickname(qq: string | number) { const { data } = await this.http.axiosRef.get( `https://api.kuizuo.cn/api/qqnick?qq=${qq}`, - ); - return data; + ) + return data } async getAvater(qq: string | number) { - return `https://q1.qlogo.cn/g?b=qq&s=100&nk=${qq}`; + return `https://q1.qlogo.cn/g?b=qq&s=100&nk=${qq}` } } diff --git a/apps/api/src/shared/shared.module.ts b/apps/api/src/shared/shared.module.ts index 0f6c982..0586d91 100644 --- a/apps/api/src/shared/shared.module.ts +++ b/apps/api/src/shared/shared.module.ts @@ -1,19 +1,19 @@ -import { RedisModule } from '@liaoliaots/nestjs-redis'; -import { HttpModule } from '@nestjs/axios'; -import { CacheModule } from '@nestjs/cache-manager'; -import { Global, Module } from '@nestjs/common'; -import { ConfigModule, ConfigService } from '@nestjs/config'; -import { ThrottlerModule } from '@nestjs/throttler'; -import { MailerModule } from '@nestjs-modules/mailer'; +import { RedisModule } from '@liaoliaots/nestjs-redis' +import { HttpModule } from '@nestjs/axios' +import { CacheModule } from '@nestjs/cache-manager' +import { Global, Module } from '@nestjs/common' +import { ConfigModule, ConfigService } from '@nestjs/config' +import { ThrottlerModule } from '@nestjs/throttler' +import { MailerModule } from '@nestjs-modules/mailer' -import { IMailerConfig, IRedisConfig } from '@/config'; +import { IMailerConfig, IRedisConfig } from '@/config' -import { IpService } from './ip/ip.service'; -import { LoggerModule } from './logger/logger.module'; -import { MailerService } from './mailer/mailer.service'; -import { QQService } from './qq/qq.service'; +import { IpService } from './ip/ip.service' +import { LoggerModule } from './logger/logger.module' +import { MailerService } from './mailer/mailer.service' +import { QQService } from './qq/qq.service' -const providers = [MailerService, IpService, QQService]; +const providers = [MailerService, IpService, QQService] @Global() @Module({ diff --git a/apps/api/src/utils/captcha.ts b/apps/api/src/utils/captcha.ts index 65ccf3d..4f416bc 100644 --- a/apps/api/src/utils/captcha.ts +++ b/apps/api/src/utils/captcha.ts @@ -1,4 +1,4 @@ -import svgCaptcha from 'svg-captcha'; +import svgCaptcha from 'svg-captcha' export function createCaptcha() { return svgCaptcha.createMathExpr({ @@ -10,10 +10,10 @@ export function createCaptcha() { fontSize: 50, width: 110, height: 38, - }); + }) } export function createMathExpr() { - const options = {}; - return svgCaptcha.createMathExpr(options); + const options = {} + return svgCaptcha.createMathExpr(options) } diff --git a/apps/api/src/utils/crypto.ts b/apps/api/src/utils/crypto.ts index 55dcc52..d801669 100644 --- a/apps/api/src/utils/crypto.ts +++ b/apps/api/src/utils/crypto.ts @@ -1,36 +1,36 @@ -import CryptoJS from 'crypto-js'; +import CryptoJS from 'crypto-js' -const key = CryptoJS.enc.Utf8.parse('kuizuoabcdefe9bc'); -const iv = CryptoJS.enc.Utf8.parse('0123456789kuizuo'); +const key = CryptoJS.enc.Utf8.parse('kuizuoabcdefe9bc') +const iv = CryptoJS.enc.Utf8.parse('0123456789kuizuo') export function AES_enc(data) { - if (!data) return data; + if (!data) return data const enc = CryptoJS.AES.encrypt(data, key, { iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7, - }); - return enc.toString(); + }) + return enc.toString() } export function AES_dec(data) { - if (!data) return data; + if (!data) return data const dec = CryptoJS.AES.decrypt(data, key, { iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7, - }); - return dec.toString(CryptoJS.enc.Utf8); + }) + return dec.toString(CryptoJS.enc.Utf8) } export function MD5(str: string) { - return CryptoJS.MD5(str).toString(); + return CryptoJS.MD5(str).toString() } export function Base64_decode(str: string) { - return CryptoJS.enc.Base64.parse(str).toString(CryptoJS.enc.Utf8); + return CryptoJS.enc.Base64.parse(str).toString(CryptoJS.enc.Utf8) } export function Base64_encode(str: string) { - return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(str)); + return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(str)) } diff --git a/apps/api/src/utils/date.ts b/apps/api/src/utils/date.ts index 077f63e..d9cf3fd 100644 --- a/apps/api/src/utils/date.ts +++ b/apps/api/src/utils/date.ts @@ -1,25 +1,25 @@ -import dayjs from 'dayjs'; -import { isDate } from 'lodash'; +import dayjs from 'dayjs' +import { isDate } from 'lodash' -const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'; -const DATE_FORMAT = 'YYYY-MM-DD'; +const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss' +const DATE_FORMAT = 'YYYY-MM-DD' export function formatToDateTime( date: string | number | Date | dayjs.Dayjs | null | undefined = undefined, format = DATE_TIME_FORMAT, ): string { - return dayjs(date).format(format); + return dayjs(date).format(format) } export function formatToDate( date: string | number | Date | dayjs.Dayjs | null | undefined = undefined, format = DATE_FORMAT, ): string { - return dayjs(date).format(format); + return dayjs(date).format(format) } export function isDateObject(obj: unknown): boolean { - return isDate(obj) || dayjs.isDayjs(obj); + return isDate(obj) || dayjs.isDayjs(obj) } -export const dateUtil = dayjs; +export const dateUtil = dayjs diff --git a/apps/api/src/utils/file.ts b/apps/api/src/utils/file.ts index 931de36..e7d95b7 100644 --- a/apps/api/src/utils/file.ts +++ b/apps/api/src/utils/file.ts @@ -1,9 +1,9 @@ -import fs from 'fs'; -import path from 'path'; +import fs from 'node:fs' +import path from 'node:path' -import { MultipartFile } from '@fastify/multipart'; +import { MultipartFile } from '@fastify/multipart' -import dayjs from 'dayjs'; +import dayjs from 'dayjs' enum Type { IMAGE = '图片', @@ -14,75 +14,75 @@ enum Type { } export function getFileType(extName: string) { - const documents = 'txt doc pdf ppt pps xlsx xls docx'; - const music = 'mp3 wav wma mpa ram ra aac aif m4a'; - const video = 'avi mpg mpe mpeg asf wmv mov qt rm mp4 flv m4v webm ogv ogg'; + const documents = 'txt doc pdf ppt pps xlsx xls docx' + const music = 'mp3 wav wma mpa ram ra aac aif m4a' + const video = 'avi mpg mpe mpeg asf wmv mov qt rm mp4 flv m4v webm ogv ogg' const image = - 'bmp dib pcp dif wmf gif jpg tif eps psd cdr iff tga pcd mpt png jpeg'; + 'bmp dib pcp dif wmf gif jpg tif eps psd cdr iff tga pcd mpt png jpeg' if (image.includes(extName)) { - return Type.IMAGE; + return Type.IMAGE } if (documents.includes(extName)) { - return Type.TXT; + return Type.TXT } if (music.includes(extName)) { - return Type.MUSIC; + return Type.MUSIC } if (video.includes(extName)) { - return Type.VIDEO; + return Type.VIDEO } - return Type.OTHER; + return Type.OTHER } export function getName(fileName: string) { if (fileName.includes('.')) { - return fileName.split('.')[0]; + return fileName.split('.')[0] } - return fileName; + return fileName } export function getExtname(fileName: string) { - return path.extname(fileName).replace('.', ''); + return path.extname(fileName).replace('.', '') } export function getSize(bytes: number, decimals = 2) { - if (bytes === 0) return '0 Bytes'; + if (bytes === 0) return '0 Bytes' - const k = 1024; - const dm = decimals < 0 ? 0 : decimals; - const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + const k = 1024 + const dm = decimals < 0 ? 0 : decimals + const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] - const i = Math.floor(Math.log(bytes) / Math.log(k)); + const i = Math.floor(Math.log(bytes) / Math.log(k)) - return `${parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}`; + return `${parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}` } export function fileRename(fileName: string) { - const name = fileName.split('.')[0]; - const extName = path.extname(fileName); - const time = dayjs().format('YYYYMMDDHHmmSSS'); - return `${name}-${time}${extName}`; + const name = fileName.split('.')[0] + const extName = path.extname(fileName) + const time = dayjs().format('YYYYMMDDHHmmSSS') + return `${name}-${time}${extName}` } export function getFilePath(name: string) { - return `/upload/${name}`; + return `/upload/${name}` } export function saveLocalFile(buffer: Buffer, name: string) { - const filePath = path.join(__dirname, '../../', 'public/upload', name); - const writeStream = fs.createWriteStream(filePath); - writeStream.write(buffer); + const filePath = path.join(__dirname, '../../', 'public/upload', name) + const writeStream = fs.createWriteStream(filePath) + writeStream.write(buffer) } export async function saveFile(file: MultipartFile, name: string) { - const filePath = path.join(__dirname, '../../', 'public/upload', name); - const writeStream = fs.createWriteStream(filePath); - const buffer = await file.toBuffer(); - writeStream.write(buffer); + const filePath = path.join(__dirname, '../../', 'public/upload', name) + const writeStream = fs.createWriteStream(filePath) + const buffer = await file.toBuffer() + writeStream.write(buffer) } export async function deleteFile(name: string) { - fs.unlink(path.join(__dirname, '../../', 'public', name), (error) => { + fs.unlink(path.join(__dirname, '../../', 'public', name), () => { // console.log(error); - }); + }) } diff --git a/apps/api/src/utils/index.ts b/apps/api/src/utils/index.ts index 937918e..1507a6c 100644 --- a/apps/api/src/utils/index.ts +++ b/apps/api/src/utils/index.ts @@ -1,6 +1,6 @@ -export * from './captcha'; -export * from './date'; -export * from './is'; -export * from './list2tree'; -export * from './crypto'; -export * from './uuid'; +export * from './captcha' +export * from './date' +export * from './is' +export * from './list2tree' +export * from './crypto' +export * from './uuid' diff --git a/apps/api/src/utils/is.ts b/apps/api/src/utils/is.ts index f3802cf..fb34db2 100644 --- a/apps/api/src/utils/is.ts +++ b/apps/api/src/utils/is.ts @@ -1,5 +1,5 @@ export function isExternal(path: string): boolean { const reg = - /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/; - return reg.test(path); + /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/ + return reg.test(path) } diff --git a/apps/api/src/utils/list2tree.ts b/apps/api/src/utils/list2tree.ts index 34c144d..f6492f8 100644 --- a/apps/api/src/utils/list2tree.ts +++ b/apps/api/src/utils/list2tree.ts @@ -1,13 +1,13 @@ export type TreeNode = T & { - id: number; - parentId: number; - children?: TreeNode[]; -}; + id: number + parentId: number + children?: TreeNode[] +} export type ListNode = T & { - id: number; - parentId: number; -}; + id: number + parentId: number +} export function list2Tree( items: T, @@ -16,12 +16,12 @@ export function list2Tree( return items .filter((item) => item.parentId === parentId) .map((item) => { - const children = list2Tree(items, item.id); + const children = list2Tree(items, item.id) return { ...item, ...(children.length ? { children } : null), - }; - }); + } + }) } /** @@ -34,21 +34,21 @@ export function list2Tree( export function filterTree2List(treeData, key, value) { const filterChildrenTree = (resTree, treeItem) => { if (treeItem[key].includes(value)) { - resTree.push(treeItem); - return resTree; + resTree.push(treeItem) + return resTree } if (Array.isArray(treeItem.children)) { - const children = treeItem.children.reduce(filterChildrenTree, []); + const children = treeItem.children.reduce(filterChildrenTree, []) - const data = { ...treeItem, children }; + const data = { ...treeItem, children } if (children.length) { - resTree.push({ ...data }); + resTree.push({ ...data }) } } - return resTree; - }; - return treeData.reduce(filterChildrenTree, []); + return resTree + } + return treeData.reduce(filterChildrenTree, []) } /** @@ -62,25 +62,25 @@ export function filterTree( predicate: (data: T) => boolean, ): TreeNode[] { function filter(treeData: TreeNode[]): TreeNode[] { - if (!treeData?.length) return treeData; + if (!treeData?.length) return treeData return treeData.filter((data) => { - if (!predicate(data)) return false; + if (!predicate(data)) return false - data.children = filter(data.children); - return true; - }); + data.children = filter(data.children) + return true + }) } - return filter(treeData) || []; + return filter(treeData) || [] } export const deleteEmptyChildren = (arr: any) => { arr?.forEach((node) => { if (node.children?.length === 0) { - delete node.children; + delete node.children } else { - deleteEmptyChildren(node.children); + deleteEmptyChildren(node.children) } - }); -}; + }) +} diff --git a/apps/api/src/utils/permission.ts b/apps/api/src/utils/permission.ts index 89c8877..6b3dae1 100644 --- a/apps/api/src/utils/permission.ts +++ b/apps/api/src/utils/permission.ts @@ -1,6 +1,6 @@ -import { isExternal } from '@/utils/is'; +import { isExternal } from '@/utils/is' -function createRoute(menu, isRoot) { +function createRoute(menu, _isRoot) { if (isExternal(menu.path)) { return { id: menu.id, @@ -8,7 +8,7 @@ function createRoute(menu, isRoot) { component: 'IFrame', name: menu.name, meta: { title: menu.name, icon: menu.icon }, - }; + } } // 目录 @@ -20,7 +20,7 @@ function createRoute(menu, isRoot) { show: true, name: menu.name, meta: { title: menu.name, icon: menu.icon }, - }; + } } return { @@ -34,29 +34,29 @@ function createRoute(menu, isRoot) { ...(!menu.show ? { hideMenu: !menu.show } : null), ignoreKeepAlive: !menu.keepalive, }, - }; + } } function filterAsyncRoutes(menus, parentRoute) { - const res = []; + const res = [] menus.forEach((menu) => { if (menu.type === 2 || !menu.status) { // 如果是权限或禁用直接跳过 - return; + return } // 根级别菜单渲染 - let realRoute; + let realRoute if (!parentRoute && !menu.parent && menu.type === 1) { // 根菜单 - realRoute = createRoute(menu, true); + realRoute = createRoute(menu, true) } else if (!parentRoute && !menu.parent && menu.type === 0) { // 目录 - const childRoutes = filterAsyncRoutes(menus, menu); - realRoute = createRoute(menu, true); + const childRoutes = filterAsyncRoutes(menus, menu) + realRoute = createRoute(menu, true) if (childRoutes && childRoutes.length > 0) { - realRoute.redirect = childRoutes[0].path; - realRoute.children = childRoutes; + realRoute.redirect = childRoutes[0].path + realRoute.children = childRoutes } } else if ( parentRoute && @@ -64,106 +64,70 @@ function filterAsyncRoutes(menus, parentRoute) { menu.type === 1 ) { // 子菜单 - realRoute = createRoute(menu, false); + realRoute = createRoute(menu, false) } else if ( parentRoute && parentRoute.id === menu.parent && menu.type === 0 ) { // 如果还是目录,继续递归 - const childRoute = filterAsyncRoutes(menus, menu); - realRoute = createRoute(menu, false); + const childRoute = filterAsyncRoutes(menus, menu) + realRoute = createRoute(menu, false) if (childRoute && childRoute.length > 0) { - realRoute.redirect = childRoute[0].path; - realRoute.children = childRoute; + realRoute.redirect = childRoute[0].path + realRoute.children = childRoute } } // add curent route if (realRoute) { - res.push(realRoute); + res.push(realRoute) } - }); - return res; + }) + return res } export function generatorRouters(menu) { - return filterAsyncRoutes(menu, null); + return filterAsyncRoutes(menu, null) } // 获取所有菜单以及权限 function filterMenuToTable(menus, parentMenu) { - const res = []; + const res = [] menus.forEach((menu) => { // 根级别菜单渲染 - let realMenu; + let realMenu if (!parentMenu && !menu.parent && menu.type === 1) { // 根菜单,查找该跟菜单下子菜单,因为可能会包含权限 - const childMenu = filterMenuToTable(menus, menu); - realMenu = { ...menu }; - realMenu.children = childMenu; + const childMenu = filterMenuToTable(menus, menu) + realMenu = { ...menu } + realMenu.children = childMenu } else if (!parentMenu && !menu.parent && menu.type === 0) { // 根目录 - const childMenu = filterMenuToTable(menus, menu); - realMenu = { ...menu }; - realMenu.children = childMenu; + const childMenu = filterMenuToTable(menus, menu) + realMenu = { ...menu } + realMenu.children = childMenu } else if (parentMenu && parentMenu.id === menu.parent && menu.type === 1) { // 子菜单下继续找是否有子菜单 - const childMenu = filterMenuToTable(menus, menu); - realMenu = { ...menu }; - realMenu.children = childMenu; + const childMenu = filterMenuToTable(menus, menu) + realMenu = { ...menu } + realMenu.children = childMenu } else if (parentMenu && parentMenu.id === menu.parent && menu.type === 0) { // 如果还是目录,继续递归 - const childMenu = filterMenuToTable(menus, menu); - realMenu = { ...menu }; - realMenu.children = childMenu; + const childMenu = filterMenuToTable(menus, menu) + realMenu = { ...menu } + realMenu.children = childMenu } else if (parentMenu && parentMenu.id === menu.parent && menu.type === 2) { - realMenu = { ...menu }; + realMenu = { ...menu } } // add curent route if (realMenu) { - realMenu.pid = menu.id; - res.push(realMenu); + realMenu.pid = menu.id + res.push(realMenu) } - }); - return res; + }) + return res } export function generatorMenu(menu) { - return filterMenuToTable(menu, null); -} - -// 仅获取所有菜单不包括权限 -function filterMenuToTree(menus, parentMenu) { - const res = []; - menus.forEach((menu) => { - // 根级别菜单渲染 - let realMenu; - if (!parentMenu && !menu.parent && menu.type === 1) { - // 根菜单,查找该跟菜单下子菜单,因为可能会包含权限 - const childMenu = filterMenuToTree(menus, menu); - realMenu = { ...menu }; - realMenu.children = childMenu; - } else if (!parentMenu && !menu.parent && menu.type === 0) { - // 根目录 - const childMenu = filterMenuToTree(menus, menu); - realMenu = { ...menu }; - realMenu.children = childMenu; - } else if (parentMenu && parentMenu.id === menu.parent && menu.type === 1) { - // 子菜单下继续找是否有子菜单 - const childMenu = filterMenuToTree(menus, menu); - realMenu = { ...menu }; - realMenu.children = childMenu; - } else if (parentMenu && parentMenu.id === menu.parent && menu.type === 0) { - // 如果还是目录,继续递归 - const childMenu = filterMenuToTree(menus, menu); - realMenu = { ...menu }; - realMenu.children = childMenu; - } - // add curent route - if (realMenu) { - realMenu.pid = menu.id; - res.push(realMenu); - } - }); - return res; + return filterMenuToTable(menu, null) } diff --git a/apps/api/src/utils/uuid.ts b/apps/api/src/utils/uuid.ts index e8e9e8d..1d6ea31 100644 --- a/apps/api/src/utils/uuid.ts +++ b/apps/api/src/utils/uuid.ts @@ -1,11 +1,11 @@ -import { nanoid } from 'nanoid'; +import { nanoid } from 'nanoid' export function generateUUID(size: number = 21): string { - return nanoid(size); + return nanoid(size) } export function generateShortUUID(): string { - return nanoid(10); + return nanoid(10) } /** @@ -15,9 +15,9 @@ export function randomValue( size = 16, dict = 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict', ): string { - let id = ''; - let i = size; - const len = dict.length; - while (i--) id += dict[(Math.random() * len) | 0]; - return id; + let id = '' + let i = size + const len = dict.length + while (i--) id += dict[(Math.random() * len) | 0] + return id }