diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..cc3ee7a --- /dev/null +++ b/.eslintignore @@ -0,0 +1,7 @@ +node_modules +dist/ +config +build/ +.eslintrc.js +package.json +tsconfig**.json diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..0b84b6b --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,22 @@ +module.exports = { + parser: '@typescript-eslint/parser', + parserOptions: { + project: 'tsconfig.json', + sourceType: 'module', + }, + plugins: ['@typescript-eslint/eslint-plugin', 'prettier'], + extends: ['plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'], + root: true, + env: { + node: true, + jest: true, + }, + ignorePatterns: ['.eslintrc.js'], + 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': 0, + }, +}; diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..c4d6feb --- /dev/null +++ b/.prettierignore @@ -0,0 +1,10 @@ +/dist/* +.local +.output.js +/node_modules/** + +**/*.svg +**/*.sh + +/public/* +test/**/* \ No newline at end of file diff --git a/.prettierrc.cjs b/.prettierrc.cjs new file mode 100644 index 0000000..bf858ce --- /dev/null +++ b/.prettierrc.cjs @@ -0,0 +1,11 @@ +module.exports = { + printWidth: 100, // 每行代码长度(默认80) + tabWidth: 2, // 每个tab相当于多少个空格(默认2) + useTabs: false, // 是否使用tab进行缩进(默认false) + singleQuote: true, // 使用单引号(默认false) + semi: true, // 声明结尾使用分号(默认true) + trailingComma: 'es5', // 多行使用拖尾逗号(默认none) + bracketSpacing: true, // 对象字面量的大括号间使用空格(默认true) + arrowParens: 'avoid', // 只有一个参数的箭头函数的参数是否带圆括号(默认avoid) + endOfLine: 'auto', // 文件换行格式 LF/CRLF +}; diff --git a/deploy/sql/contracts.sql b/deploy/sql/contracts.sql index 307c789..b010e9f 100644 --- a/deploy/sql/contracts.sql +++ b/deploy/sql/contracts.sql @@ -1,7 +1,8 @@ CREATE TABLE contracts ( - id INT AUTO_INCREMENT PRIMARY KEY COMMENT '合同编号', - contract_title VARCHAR(255) COMMENT '合同标题', - contract_type VARCHAR(255) COMMENT '合同类型', + id INT AUTO_INCREMENT PRIMARY KEY , + contract_number VARCHAR(255) COMMENT '合同编号', + title VARCHAR(255) COMMENT '合同标题', + type VARCHAR(255) COMMENT '合同类型', party_a VARCHAR(255) COMMENT '甲方', party_b VARCHAR(255) COMMENT '乙方', signing_date DATE COMMENT '签订日期', diff --git a/package.json b/package.json index 8126b51..711305a 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "c": "git add . && git cz && git push", "release": "standard-version", "commitlint": "commitlint --config commitlint.config.cjs -e -V", - "lint:prettier": "prettier --write \"**/*.{js,cjs,ts,json,md}\"" + "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"" }, "dependencies": { "@fastify/cookie": "^9.3.1", @@ -108,7 +108,6 @@ "winston-daily-rotate-file": "^5.0.0" }, "devDependencies": { - "@antfu/eslint-config": "^2.6.4", "@compodoc/compodoc": "^1.1.23", "@nestjs/cli": "^10.3.2", "@nestjs/schematics": "^10.1.1", @@ -119,10 +118,14 @@ "@types/node": "^20.11.16", "@types/supertest": "^6.0.2", "@types/ua-parser-js": "^0.7.39", + "@typescript-eslint/eslint-plugin": "^5.0.0", + "@typescript-eslint/parser": "^5.0.0", "cross-env": "^7.0.3", - "eslint": "^8.56.0", + "eslint": "^8.0.1", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-prettier": "^4.0.0", + "prettier": "~3.2.5", "jest": "^29.7.0", - "lint-staged": "^15.2.2", "source-map-support": "^0.5.21", "supertest": "^6.3.4", "ts-jest": "^29.1.2", @@ -136,11 +139,6 @@ "standard-version": "^9.5.0", "husky": "^8.0.0" }, - "pnpm": { - "overrides": { - "@liaoliaots/nestjs-redis": "npm:@songkeys/nestjs-redis" - } - }, "config": { "commitizen": { "path": "cz-customizable" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f084228..e03c7c8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -238,6 +238,18 @@ devDependencies: eslint: specifier: ^8.56.0 version: 8.56.0 + eslint-config-prettier: + specifier: ~9.1.0 + version: 9.1.0(eslint@8.56.0) + eslint-define-config: + specifier: ~2.1.0 + version: 2.1.0 + eslint-plugin-import: + specifier: ~2.29.1 + version: 2.29.1(@typescript-eslint/parser@6.21.0)(eslint@8.56.0) + eslint-plugin-prettier: + specifier: ~5.1.3 + version: 5.1.3(eslint-config-prettier@9.1.0)(eslint@8.56.0)(prettier@3.2.5) husky: specifier: ^8.0.0 version: 8.0.3 @@ -247,6 +259,9 @@ devDependencies: lint-staged: specifier: ^15.2.2 version: 15.2.2 + prettier: + specifier: ~3.2.5 + version: 3.2.5 source-map-support: specifier: ^0.5.21 version: 0.5.21 @@ -2921,6 +2936,11 @@ packages: requiresBuild: true optional: true + /@pkgr/core@0.1.1: + resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + dev: true + /@selderee/plugin-htmlparser2@0.11.0: resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==} dependencies: @@ -3228,6 +3248,10 @@ packages: resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} dev: true + /@types/json5@0.0.29: + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + dev: true + /@types/jsonwebtoken@9.0.5: resolution: {integrity: sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==} dependencies: @@ -3920,6 +3944,14 @@ packages: /argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + /array-buffer-byte-length@1.0.1: + resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + is-array-buffer: 3.0.4 + dev: true + /array-from@2.1.1: resolution: {integrity: sha512-GQTc6Uupx1FCavi5mPzBvVT7nEOeWMmUA9P95wpfpW1XwMSKs+KaymD5C2Up7KAUKg/mYwbsUYzdZWcoajlNZg==} dev: true @@ -3928,6 +3960,17 @@ packages: resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} dev: true + /array-includes@3.1.7: + resolution: {integrity: sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.22.4 + get-intrinsic: 1.2.4 + is-string: 1.0.7 + dev: true + /array-timsort@1.0.3: resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} dev: true @@ -3937,6 +3980,62 @@ packages: engines: {node: '>=8'} dev: true + /array.prototype.filter@1.0.3: + resolution: {integrity: sha512-VizNcj/RGJiUyQBgzwxzE5oHdeuXY5hSbbmKMlphj1cy1Vl7Pn2asCGbSrru6hSQjmCzqTBPVWAF/whmEOVHbw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.22.4 + es-array-method-boxes-properly: 1.0.0 + is-string: 1.0.7 + dev: true + + /array.prototype.findlastindex@1.2.4: + resolution: {integrity: sha512-hzvSHUshSpCflDR1QMUBLHGHP1VIEBegT4pix9H/Z92Xw3ySoy6c2qh7lJWTJnRJ8JCZ9bJNCgTyYaJGcJu6xQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.22.4 + es-errors: 1.3.0 + es-shim-unscopables: 1.0.2 + dev: true + + /array.prototype.flat@1.3.2: + resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.22.4 + es-shim-unscopables: 1.0.2 + dev: true + + /array.prototype.flatmap@1.3.2: + resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.22.4 + es-shim-unscopables: 1.0.2 + dev: true + + /arraybuffer.prototype.slice@1.0.3: + resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} + engines: {node: '>= 0.4'} + dependencies: + array-buffer-byte-length: 1.0.1 + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.22.4 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + is-array-buffer: 3.0.4 + is-shared-array-buffer: 1.0.3 + dev: true + /arrify@1.0.1: resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} engines: {node: '>=0.10.0'} @@ -3980,6 +4079,13 @@ packages: engines: {node: '>=8.0.0'} dev: false + /available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + dependencies: + possible-typed-array-names: 1.0.0 + dev: true + /avvio@8.3.0: resolution: {integrity: sha512-VBVH0jubFr9LdFASy/vNtm5giTrnbVquWBhT0fyizuNK2rQ7e7ONU2plZQWUNqtE1EmxFEb+kbSkFRkstiaS9Q==} dependencies: @@ -5452,6 +5558,13 @@ packages: run-applescript: 3.2.0 dev: false + /doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + dependencies: + esutils: 2.0.3 + dev: true + /doctrine@3.0.0: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} @@ -5689,6 +5802,57 @@ packages: stackframe: 1.3.4 dev: false + /es-abstract@1.22.4: + resolution: {integrity: sha512-vZYJlk2u6qHYxBOTjAeg7qUxHdNfih64Uu2J8QqWgXZ2cri0ZpJAkzDUK/q593+mvKwlxyaxr6F1Q+3LKoQRgg==} + engines: {node: '>= 0.4'} + dependencies: + array-buffer-byte-length: 1.0.1 + arraybuffer.prototype.slice: 1.0.3 + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + es-define-property: 1.0.0 + es-errors: 1.3.0 + es-set-tostringtag: 2.0.3 + es-to-primitive: 1.2.1 + function.prototype.name: 1.1.6 + get-intrinsic: 1.2.4 + get-symbol-description: 1.0.2 + globalthis: 1.0.3 + gopd: 1.0.1 + has-property-descriptors: 1.0.2 + has-proto: 1.0.1 + has-symbols: 1.0.3 + hasown: 2.0.1 + internal-slot: 1.0.7 + is-array-buffer: 3.0.4 + is-callable: 1.2.7 + is-negative-zero: 2.0.3 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.3 + is-string: 1.0.7 + is-typed-array: 1.1.13 + is-weakref: 1.0.2 + object-inspect: 1.13.1 + object-keys: 1.1.1 + object.assign: 4.1.5 + regexp.prototype.flags: 1.5.2 + safe-array-concat: 1.1.0 + safe-regex-test: 1.0.3 + string.prototype.trim: 1.2.8 + string.prototype.trimend: 1.0.7 + string.prototype.trimstart: 1.0.7 + typed-array-buffer: 1.0.2 + typed-array-byte-length: 1.0.1 + typed-array-byte-offset: 1.0.2 + typed-array-length: 1.0.5 + unbox-primitive: 1.0.2 + which-typed-array: 1.1.14 + dev: true + + /es-array-method-boxes-properly@1.0.0: + resolution: {integrity: sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==} + dev: true + /es-define-property@1.0.0: resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} engines: {node: '>= 0.4'} @@ -5703,6 +5867,30 @@ packages: resolution: {integrity: sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==} dev: true + /es-set-tostringtag@2.0.3: + resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.4 + has-tostringtag: 1.0.2 + hasown: 2.0.1 + dev: true + + /es-shim-unscopables@1.0.2: + resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} + dependencies: + hasown: 2.0.1 + dev: true + + /es-to-primitive@1.2.1: + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + engines: {node: '>= 0.4'} + dependencies: + is-callable: 1.2.7 + is-date-object: 1.0.5 + is-symbol: 1.0.4 + dev: true + /es5-ext@0.10.62: resolution: {integrity: sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==} engines: {node: '>=0.10'} @@ -5855,6 +6043,20 @@ packages: parse-gitignore: 2.0.0 dev: true + /eslint-config-prettier@9.1.0(eslint@8.56.0): + resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + dependencies: + eslint: 8.56.0 + dev: true + + /eslint-define-config@2.1.0: + resolution: {integrity: sha512-QUp6pM9pjKEVannNAbSJNeRuYwW3LshejfyBBpjeMGaJjaDUpVps4C6KVR8R7dWZnD3i0synmrE36znjTkJvdQ==} + engines: {node: '>=18.0.0', npm: '>=9.0.0', pnpm: '>=8.6.0'} + dev: true + /eslint-import-resolver-node@0.3.9: resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} dependencies: @@ -5955,6 +6157,41 @@ packages: - supports-color dev: true + /eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0)(eslint@8.56.0): + resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + dependencies: + '@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3) + array-includes: 3.1.7 + array.prototype.findlastindex: 1.2.4 + array.prototype.flat: 1.3.2 + array.prototype.flatmap: 1.3.2 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 8.56.0 + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint@8.56.0) + hasown: 2.0.1 + is-core-module: 2.13.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.7 + object.groupby: 1.0.2 + object.values: 1.1.7 + semver: 6.3.1 + tsconfig-paths: 3.15.0 + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + dev: true + /eslint-plugin-jsdoc@48.1.0(eslint@8.56.0): resolution: {integrity: sha512-g9S8ukmTd1DVcV/xeBYPPXOZ6rc8WJ4yi0+MVxJ1jBOrz5kmxV9gJJQ64ltCqIWFnBChLIhLVx3tbTSarqVyFA==} engines: {node: '>=18'} @@ -6056,6 +6293,27 @@ packages: - typescript dev: true + /eslint-plugin-prettier@5.1.3(eslint-config-prettier@9.1.0)(eslint@8.56.0)(prettier@3.2.5): + resolution: {integrity: sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '*' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + dependencies: + eslint: 8.56.0 + eslint-config-prettier: 9.1.0(eslint@8.56.0) + prettier: 3.2.5 + prettier-linter-helpers: 1.0.0 + synckit: 0.8.8 + dev: true + /eslint-plugin-toml@0.9.2(eslint@8.56.0): resolution: {integrity: sha512-ri0xf63PYf3pIq/WY9BIwrqxZmGTIwSkAO0bHddI0ajUwN4KGz6W8vOvdXFHOpRdRfzxlmXze/vfsY/aTEXESg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -6496,6 +6754,10 @@ packages: /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + /fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + dev: true + /fast-glob@3.3.2: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} @@ -6773,6 +7035,12 @@ packages: optional: true dev: false + /for-each@0.3.3: + resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + dependencies: + is-callable: 1.2.7 + dev: true + /foreground-child@3.1.1: resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} engines: {node: '>=14'} @@ -6887,6 +7155,16 @@ packages: /function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + /function.prototype.name@1.1.6: + resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.22.4 + functions-have-names: 1.2.3 + dev: true + /functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} dev: true @@ -6968,6 +7246,15 @@ packages: engines: {node: '>=16'} dev: true + /get-symbol-description@1.0.2: + resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + dev: true + /get-tsconfig@4.7.2: resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==} dependencies: @@ -7109,6 +7396,13 @@ packages: type-fest: 0.20.2 dev: true + /globalthis@1.0.3: + resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} + engines: {node: '>= 0.4'} + dependencies: + define-properties: 1.2.1 + dev: true + /globby@11.1.0: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} @@ -7156,6 +7450,10 @@ packages: engines: {node: '>=6'} dev: true + /has-bigints@1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + dev: true + /has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} @@ -7179,6 +7477,11 @@ packages: resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} engines: {node: '>= 0.4'} + /has-proto@1.0.3: + resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} + engines: {node: '>= 0.4'} + dev: true + /has-symbols@1.0.3: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} @@ -7504,6 +7807,15 @@ packages: wrap-ansi: 6.2.0 dev: true + /internal-slot@1.0.7: + resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} + engines: {node: '>= 0.4'} + dependencies: + es-errors: 1.3.0 + hasown: 2.0.1 + side-channel: 1.0.5 + dev: true + /interpret@1.4.0: resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==} engines: {node: '>= 0.10'} @@ -7554,6 +7866,14 @@ packages: has-tostringtag: 1.0.2 dev: true + /is-array-buffer@3.0.4: + resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + dev: true + /is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} dev: true @@ -7562,12 +7882,26 @@ packages: resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} dev: false + /is-bigint@1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + dependencies: + has-bigints: 1.0.2 + dev: true + /is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} dependencies: binary-extensions: 2.2.0 + /is-boolean-object@1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + has-tostringtag: 1.0.2 + dev: true + /is-builtin-module@3.2.1: resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==} engines: {node: '>=6'} @@ -7575,6 +7909,11 @@ packages: builtin-modules: 3.3.0 dev: true + /is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + dev: true + /is-core-module@2.13.1: resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} dependencies: @@ -7654,6 +7993,18 @@ packages: engines: {node: '>=8'} dev: true + /is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + dev: true + + /is-number-object@1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.2 + dev: true + /is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -7689,6 +8040,13 @@ packages: call-bind: 1.0.7 has-tostringtag: 1.0.2 + /is-shared-array-buffer@1.0.3: + resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + dev: true + /is-stream@1.1.0: resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} engines: {node: '>=0.10.0'} @@ -7703,6 +8061,20 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dev: true + /is-string@1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.2 + dev: true + + /is-symbol@1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + dev: true + /is-text-path@1.0.1: resolution: {integrity: sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==} engines: {node: '>=0.10.0'} @@ -7710,6 +8082,13 @@ packages: text-extensions: 1.9.0 dev: true + /is-typed-array@1.1.13: + resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} + engines: {node: '>= 0.4'} + dependencies: + which-typed-array: 1.1.14 + dev: true + /is-unicode-supported@0.1.0: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} @@ -7724,6 +8103,12 @@ packages: resolution: {integrity: sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==} dev: true + /is-weakref@1.0.2: + resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + dependencies: + call-bind: 1.0.7 + dev: true + /is-windows@1.0.2: resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} engines: {node: '>=0.10.0'} @@ -7739,6 +8124,10 @@ packages: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} dev: true + /isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + dev: true + /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -8342,6 +8731,13 @@ packages: resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} dev: true + /json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + dependencies: + minimist: 1.2.8 + dev: true + /json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -9728,6 +10124,44 @@ packages: engines: {node: '>= 0.4'} dev: true + /object.assign@4.1.5: + resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + has-symbols: 1.0.3 + object-keys: 1.1.1 + dev: true + + /object.fromentries@2.0.7: + resolution: {integrity: sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.22.4 + dev: true + + /object.groupby@1.0.2: + resolution: {integrity: sha512-bzBq58S+x+uo0VjurFT0UktpKHOZmv4/xePiOA1nbB9pMqpGK7rUPNgf+1YC+7mE+0HzhTMqNUuCqvKhj6FnBw==} + dependencies: + array.prototype.filter: 1.0.3 + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.22.4 + es-errors: 1.3.0 + dev: true + + /object.values@1.1.7: + resolution: {integrity: sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.22.4 + dev: true + /obliterator@2.0.4: resolution: {integrity: sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==} dev: false @@ -10322,6 +10756,11 @@ packages: resolution: {integrity: sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==} dev: true + /possible-typed-array-names@1.0.0: + resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} + engines: {node: '>= 0.4'} + dev: true + /postcss-selector-parser@6.0.15: resolution: {integrity: sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==} engines: {node: '>=4'} @@ -10349,6 +10788,19 @@ packages: engines: {node: '>= 0.8.0'} dev: true + /prettier-linter-helpers@1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} + dependencies: + fast-diff: 1.3.0 + dev: true + + /prettier@3.2.5: + resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==} + engines: {node: '>=14'} + hasBin: true + dev: true + /pretty-format@29.7.0: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -10985,6 +11437,16 @@ packages: dependencies: tslib: 2.6.2 + /safe-array-concat@1.1.0: + resolution: {integrity: sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==} + engines: {node: '>=0.4'} + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + has-symbols: 1.0.3 + isarray: 2.0.5 + dev: true + /safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} dev: true @@ -10992,6 +11454,15 @@ packages: /safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + /safe-regex-test@1.0.3: + resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-regex: 1.1.4 + dev: true + /safe-regex2@2.0.0: resolution: {integrity: sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ==} dependencies: @@ -11527,6 +11998,31 @@ packages: strip-ansi: 7.1.0 dev: true + /string.prototype.trim@1.2.8: + resolution: {integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.22.4 + dev: true + + /string.prototype.trimend@1.0.7: + resolution: {integrity: sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==} + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.22.4 + dev: true + + /string.prototype.trimstart@1.0.7: + resolution: {integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==} + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.22.4 + dev: true + /string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} dependencies: @@ -11690,6 +12186,14 @@ packages: tslib: 2.6.2 dev: true + /synckit@0.8.8: + resolution: {integrity: sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==} + engines: {node: ^14.18.0 || >=16.0.0} + dependencies: + '@pkgr/core': 0.1.1 + tslib: 2.6.2 + dev: true + /systeminformation@5.22.0: resolution: {integrity: sha512-oAP80ymt8ssrAzjX8k3frbL7ys6AotqC35oikG6/SG15wBw+tG9nCk4oPaXIhEaAOAZ8XngxUv3ORq2IuR3r4Q==} engines: {node: '>=8.0.0'} @@ -11988,6 +12492,15 @@ packages: tsconfig-paths: 4.2.0 dev: true + /tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.8 + strip-bom: 3.0.0 + dev: true + /tsconfig-paths@4.2.0: resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} engines: {node: '>=6'} @@ -12070,6 +12583,50 @@ packages: resolution: {integrity: sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==} dev: true + /typed-array-buffer@1.0.2: + resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-typed-array: 1.1.13 + dev: true + + /typed-array-byte-length@1.0.1: + resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + dev: true + + /typed-array-byte-offset@1.0.2: + resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + dev: true + + /typed-array-length@1.0.5: + resolution: {integrity: sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + possible-typed-array-names: 1.0.0 + dev: true + /typedarray@0.0.6: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} dev: true @@ -12191,6 +12748,15 @@ packages: dependencies: '@lukeed/csprng': 1.1.0 + /unbox-primitive@1.0.2: + resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + dependencies: + call-bind: 1.0.7 + has-bigints: 1.0.2 + has-symbols: 1.0.3 + which-boxed-primitive: 1.0.2 + dev: true + /undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} @@ -12517,6 +13083,27 @@ packages: tr46: 0.0.3 webidl-conversions: 3.0.1 + /which-boxed-primitive@1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + dependencies: + is-bigint: 1.0.4 + is-boolean-object: 1.1.2 + is-number-object: 1.0.7 + is-string: 1.0.7 + is-symbol: 1.0.4 + dev: true + + /which-typed-array@1.1.14: + resolution: {integrity: sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.2 + dev: true + /which@1.3.1: resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} hasBin: true diff --git a/src/app.module.ts b/src/app.module.ts index 34b38f7..7b5542d 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,29 +1,30 @@ -import { ClassSerializerInterceptor, Module } from '@nestjs/common' +import { ClassSerializerInterceptor, Module } from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config' -import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core' +import { ConfigModule } from '@nestjs/config'; +import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core'; -import config from '~/config' -import { SharedModule } from '~/shared/shared.module' +import config from '~/config'; +import { SharedModule } from '~/shared/shared.module'; -import { AllExceptionsFilter } from './common/filters/any-exception.filter' +import { AllExceptionsFilter } from './common/filters/any-exception.filter'; -import { IdempotenceInterceptor } from './common/interceptors/idempotence.interceptor' -import { TimeoutInterceptor } from './common/interceptors/timeout.interceptor' -import { TransformInterceptor } from './common/interceptors/transform.interceptor' -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 { NetdiskModule } from './modules/netdisk/netdisk.module' -import { SseModule } from './modules/sse/sse.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' +import { IdempotenceInterceptor } from './common/interceptors/idempotence.interceptor'; +import { TimeoutInterceptor } from './common/interceptors/timeout.interceptor'; +import { TransformInterceptor } from './common/interceptors/transform.interceptor'; +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 { NetdiskModule } from './modules/netdisk/netdisk.module'; +import { SseModule } from './modules/sse/sse.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'; -import { SocketModule } from './socket/socket.module' +import { SocketModule } from './socket/socket.module'; +import { ContractModule } from './modules/contract/contract.module'; @Module({ imports: [ @@ -51,6 +52,8 @@ import { SocketModule } from './socket/socket.module' // end biz TodoModule, + + ContractModule, ], providers: [ { provide: APP_FILTER, useClass: AllExceptionsFilter }, diff --git a/src/common/adapters/fastify.adapter.ts b/src/common/adapters/fastify.adapter.ts index e25d0ca..67ecb8c 100644 --- a/src/common/adapters/fastify.adapter.ts +++ b/src/common/adapters/fastify.adapter.ts @@ -1,13 +1,13 @@ -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, // forceCloseConnections: true, -}) -export { app as fastifyApp } +}); +export { app as fastifyApp }; app.register(FastifyMultipart, { limits: { @@ -15,32 +15,30 @@ app.register(FastifyMultipart, { fileSize: 1024 * 1024 * 6, // limit size 6M files: 5, // Max number of file fields }, -}) +}); app.register(FastifyCookie, { secret: 'cookie-secret', // 这个 secret 不太重要,不存鉴权相关,无关紧要 -}) +}); app.getInstance().addHook('onRequest', (request, reply, done) => { // set undefined origin - const { origin } = request.headers - if (!origin) - request.headers.origin = request.headers.host + const { origin } = request.headers; + if (!origin) 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.' + 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.'; - 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() + if (url.match(/favicon.ico$/) || url.match(/manifest.json$/)) return reply.code(204).send(); - done() -}) + done(); +}); diff --git a/src/common/adapters/socket.adapter.ts b/src/common/adapters/socket.adapter.ts index d766592..ce50f40 100644 --- a/src/common/adapters/socket.adapter.ts +++ b/src/common/adapters/socket.adapter.ts @@ -1,26 +1,26 @@ -import { INestApplication } from '@nestjs/common' -import { IoAdapter } from '@nestjs/platform-socket.io' -import { createAdapter } from '@socket.io/redis-adapter' +import { INestApplication } from '@nestjs/common'; +import { IoAdapter } from '@nestjs/platform-socket.io'; +import { createAdapter } from '@socket.io/redis-adapter'; -import { REDIS_PUBSUB } from '~/shared/redis/redis.constant' +import { REDIS_PUBSUB } from '~/shared/redis/redis.constant'; -export const RedisIoAdapterKey = 'm-shop-socket' +export const RedisIoAdapterKey = 'm-shop-socket'; export class RedisIoAdapter extends IoAdapter { constructor(private readonly app: INestApplication) { - super(app) + super(app); } createIOServer(port: number, options?: any) { - const server = super.createIOServer(port, options) + const server = super.createIOServer(port, options); - const { pubClient, subClient } = this.app.get(REDIS_PUBSUB) + const { pubClient, subClient } = this.app.get(REDIS_PUBSUB); const redisAdapter = createAdapter(pubClient, subClient, { key: RedisIoAdapterKey, requestsTimeout: 10000, - }) - server.adapter(redisAdapter) - return server + }); + server.adapter(redisAdapter); + return server; } } diff --git a/src/common/decorators/api-result.decorator.ts b/src/common/decorators/api-result.decorator.ts index 1892762..65887fa 100644 --- a/src/common/decorators/api-result.decorator.ts +++ b/src/common/decorators/api-result.decorator.ts @@ -1,15 +1,13 @@ -import { HttpStatus, Type, applyDecorators } from '@nestjs/common' -import { ApiExtraModels, ApiResponse, getSchemaPath } from '@nestjs/swagger' +import { HttpStatus, Type, applyDecorators } 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']; function genBaseProp(type: Type) { - if (baseTypeNames.includes(type.name)) - return { type: type.name.toLocaleLowerCase() } - else - return { $ref: getSchemaPath(type) } + if (baseTypeNames.includes(type.name)) return { type: type.name.toLocaleLowerCase() }; + else return { $ref: getSchemaPath(type) }; } /** @@ -20,11 +18,11 @@ export function 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) { @@ -46,23 +44,20 @@ export function ApiResult>({ }, }, }, - } - } - else { + }; + } else { prop = { type: 'array', items: genBaseProp(type[0]), - } + }; } - } - else if (type) { - prop = genBaseProp(type) - } - else { - prop = { type: 'null', default: null } + } else if (type) { + prop = genBaseProp(type); + } else { + 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), @@ -78,6 +73,6 @@ export function ApiResult>({ }, ], }, - }), - ) + }) + ); } diff --git a/src/common/decorators/bypass.decorator.ts b/src/common/decorators/bypass.decorator.ts index 8c7562d..eb66876 100644 --- a/src/common/decorators/bypass.decorator.ts +++ b/src/common/decorators/bypass.decorator.ts @@ -1,10 +1,10 @@ -import { SetMetadata } from '@nestjs/common' +import { SetMetadata } from '@nestjs/common'; -export const BYPASS_KEY = '__bypass_key__' +export const BYPASS_KEY = '__bypass_key__'; /** * 当不需要转换成基础返回格式时添加该装饰器 */ export function Bypass() { - return SetMetadata(BYPASS_KEY, true) + return SetMetadata(BYPASS_KEY, true); } diff --git a/src/common/decorators/cookie.decorator.ts b/src/common/decorators/cookie.decorator.ts index 102a527..9b91ba5 100644 --- a/src/common/decorators/cookie.decorator.ts +++ b/src/common/decorators/cookie.decorator.ts @@ -1,8 +1,8 @@ -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/src/common/decorators/cron-once.decorator.ts b/src/common/decorators/cron-once.decorator.ts index 39eab20..36267e9 100644 --- a/src/common/decorators/cron-once.decorator.ts +++ b/src/common/decorators/cron-once.decorator.ts @@ -1,19 +1,19 @@ -import cluster from 'node:cluster' +import cluster from 'node:cluster'; -import { Cron } from '@nestjs/schedule' +import { Cron } from '@nestjs/schedule'; -import { isMainProcess } from '~/global/env' +import { isMainProcess } from '~/global/env'; export const CronOnce: typeof Cron = (...rest): MethodDecorator => { // If not in cluster mode, and PM2 main worker if (isMainProcess) // eslint-disable-next-line no-useless-call - return Cron.call(null, ...rest) + return Cron.call(null, ...rest); if (cluster.isWorker && cluster.worker?.id === 1) // eslint-disable-next-line no-useless-call - return Cron.call(null, ...rest) + return Cron.call(null, ...rest); - const returnNothing: MethodDecorator = () => {} - return returnNothing -} + const returnNothing: MethodDecorator = () => {}; + return returnNothing; +}; diff --git a/src/common/decorators/field.decorator.ts b/src/common/decorators/field.decorator.ts index 68fa483..f3863f2 100644 --- a/src/common/decorators/field.decorator.ts +++ b/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,115 +23,86 @@ 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 +export function NumberField(options: INumberFieldOptions = {}): PropertyDecorator { + const { each, min, max, int, positive, required = true } = options; - const decorators = [ToNumber()] + const decorators = [ToNumber()]; - if (each) - decorators.push(ToArray()) + if (each) decorators.push(ToArray()); - if (int) - decorators.push(IsInt({ each })) - else - decorators.push(IsNumber({}, { each })) + if (int) decorators.push(IsInt({ each })); + else decorators.push(IsNumber({}, { each })); - if (isNumber(min)) - decorators.push(Min(min, { each })) + if (isNumber(min)) decorators.push(Min(min, { each })); - if (isNumber(max)) - decorators.push(Max(max, { each })) + if (isNumber(max)) decorators.push(Max(max, { each })); - if (positive) - decorators.push(IsPositive({ each })) + if (positive) decorators.push(IsPositive({ each })); - if (!required) - decorators.push(IsOptional()) + if (!required) decorators.push(IsOptional()); - return applyDecorators(...decorators) + return applyDecorators(...decorators); } -export function StringField( - options: IStringFieldOptions = {}, -): PropertyDecorator { - const { - each, - minLength, - maxLength, - lowerCase, - upperCase, - required = true, - } = options +export function StringField(options: IStringFieldOptions = {}): PropertyDecorator { + const { each, minLength, maxLength, lowerCase, upperCase, required = true } = options; - const decorators = [IsString({ each }), ToTrim()] + const decorators = [IsString({ each }), ToTrim()]; - if (each) - decorators.push(ToArray()) + if (each) decorators.push(ToArray()); - if (isNumber(minLength)) - decorators.push(MinLength(minLength, { each })) + if (isNumber(minLength)) decorators.push(MinLength(minLength, { each })); - if (isNumber(maxLength)) - decorators.push(MaxLength(maxLength, { each })) + if (isNumber(maxLength)) decorators.push(MaxLength(maxLength, { each })); - if (lowerCase) - decorators.push(ToLowerCase()) + if (lowerCase) decorators.push(ToLowerCase()); - if (upperCase) - decorators.push(ToUpperCase()) + if (upperCase) decorators.push(ToUpperCase()); - if (!required) - decorators.push(IsOptional()) - else - decorators.push(IsNotEmpty({ each })) + if (!required) decorators.push(IsOptional()); + else decorators.push(IsNotEmpty({ each })); - return applyDecorators(...decorators) + return applyDecorators(...decorators); } -export function BooleanField( - options: IOptionalOptions = {}, -): PropertyDecorator { - const decorators = [ToBoolean(), IsBoolean()] +export function BooleanField(options: IOptionalOptions = {}): PropertyDecorator { + const decorators = [ToBoolean(), IsBoolean()]; - const { required = true } = options + const { required = true } = options; - if (!required) - decorators.push(IsOptional()) + if (!required) 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()) + if (!required) decorators.push(IsOptional()); - return applyDecorators(...decorators) + return applyDecorators(...decorators); } diff --git a/src/common/decorators/http.decorator.ts b/src/common/decorators/http.decorator.ts index 85ed449..22c13f7 100644 --- a/src/common/decorators/http.decorator.ts +++ b/src/common/decorators/http.decorator.ts @@ -1,22 +1,22 @@ -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'; -import { getIp } from '~/utils/ip.util' +import { getIp } from '~/utils/ip.util'; /** * 快速获取IP */ export const Ip = createParamDecorator((_, context: ExecutionContext) => { - const request = context.switchToHttp().getRequest() - return getIp(request) -}) + const request = context.switchToHttp().getRequest(); + return getIp(request); +}); /** * 快速获取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/src/common/decorators/id-param.decorator.ts b/src/common/decorators/id-param.decorator.ts index fade33c..4967ce2 100644 --- a/src/common/decorators/id-param.decorator.ts +++ b/src/common/decorators/id-param.decorator.ts @@ -1,7 +1,13 @@ -import { HttpStatus, NotAcceptableException, Param, ParseIntPipe } from '@nestjs/common' +import { HttpStatus, NotAcceptableException, Param, ParseIntPipe } from '@nestjs/common'; export function IdParam() { - return Param('id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE, exceptionFactory: (_error) => { - throw new NotAcceptableException('id 格式不正确') - } })) + return Param( + 'id', + new ParseIntPipe({ + errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE, + exceptionFactory: _error => { + throw new NotAcceptableException('id 格式不正确'); + }, + }) + ); } diff --git a/src/common/decorators/idempotence.decorator.ts b/src/common/decorators/idempotence.decorator.ts index d1142b3..949b321 100644 --- a/src/common/decorators/idempotence.decorator.ts +++ b/src/common/decorators/idempotence.decorator.ts @@ -1,15 +1,15 @@ -import { SetMetadata } from '@nestjs/common' +import { SetMetadata } from '@nestjs/common'; -import { IdempotenceOption } from '../interceptors/idempotence.interceptor' +import { IdempotenceOption } from '../interceptors/idempotence.interceptor'; -export const HTTP_IDEMPOTENCE_KEY = '__idempotence_key__' -export const HTTP_IDEMPOTENCE_OPTIONS = '__idempotence_options__' +export const HTTP_IDEMPOTENCE_KEY = '__idempotence_key__'; +export const HTTP_IDEMPOTENCE_OPTIONS = '__idempotence_options__'; /** * 幂等 */ export function Idempotence(options?: IdempotenceOption): MethodDecorator { return function (target, key, descriptor: PropertyDescriptor) { - SetMetadata(HTTP_IDEMPOTENCE_OPTIONS, options || {})(descriptor.value) - } + SetMetadata(HTTP_IDEMPOTENCE_OPTIONS, options || {})(descriptor.value); + }; } diff --git a/src/common/decorators/swagger.decorator.ts b/src/common/decorators/swagger.decorator.ts index 4809e5c..92744d8 100644 --- a/src/common/decorators/swagger.decorator.ts +++ b/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/src/common/decorators/transform.decorator.ts b/src/common/decorators/transform.decorator.ts index e86cf75..f69dc98 100644 --- a/src/common/decorators/transform.decorator.ts +++ b/src/common/decorators/transform.decorator.ts @@ -1,21 +1,20 @@ -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 */ export function ToNumber(): PropertyDecorator { return Transform( - (params) => { - const value = params.value as string[] | string + params => { + const value = params.value as string[] | string; - if (isArray(value)) - return value.map(v => Number(v)) + if (isArray(value)) return value.map(v => Number(v)); - return Number(value) + return Number(value); }, - { toClassOnly: true }, - ) + { toClassOnly: true } + ); } /** @@ -23,16 +22,15 @@ export function ToNumber(): PropertyDecorator { */ export function ToInt(): PropertyDecorator { return Transform( - (params) => { - const value = params.value as string[] | string + params => { + const value = params.value as string[] | string; - if (isArray(value)) - return value.map(v => Number.parseInt(v)) + if (isArray(value)) return value.map(v => Number.parseInt(v)); - return Number.parseInt(value) + return Number.parseInt(value); }, - { toClassOnly: true }, - ) + { toClassOnly: true } + ); } /** @@ -40,18 +38,18 @@ export function ToInt(): PropertyDecorator { */ export function ToBoolean(): PropertyDecorator { return Transform( - (params) => { + 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 }, - ) + { toClassOnly: true } + ); } /** @@ -59,16 +57,15 @@ export function ToBoolean(): PropertyDecorator { */ export function ToDate(): PropertyDecorator { return Transform( - (params) => { - const { value } = params + params => { + const { value } = params; - if (!value) - return + if (!value) return; - return new Date(value) + return new Date(value); }, - { toClassOnly: true }, - ) + { toClassOnly: true } + ); } /** @@ -76,16 +73,15 @@ export function ToDate(): PropertyDecorator { */ export function ToArray(): PropertyDecorator { return Transform( - (params) => { - const { value } = params + params => { + const { value } = params; - if (isNil(value)) - return [] + if (isNil(value)) return []; - return castArray(value) + return castArray(value); }, - { toClassOnly: true }, - ) + { toClassOnly: true } + ); } /** @@ -93,16 +89,15 @@ export function ToArray(): PropertyDecorator { */ export function ToTrim(): PropertyDecorator { return Transform( - (params) => { - const value = params.value as string[] | string + params => { + const value = params.value as string[] | string; - if (isArray(value)) - return value.map(v => trim(v)) + if (isArray(value)) return value.map(v => trim(v)); - return trim(value) + return trim(value); }, - { toClassOnly: true }, - ) + { toClassOnly: true } + ); } /** @@ -110,19 +105,17 @@ export function ToTrim(): PropertyDecorator { */ export function ToLowerCase(): PropertyDecorator { return Transform( - (params) => { - const value = params.value as string[] | string + params => { + const value = params.value as string[] | string; - if (!value) - return + if (!value) return; - if (isArray(value)) - return value.map(v => v.toLowerCase()) + if (isArray(value)) return value.map(v => v.toLowerCase()); - return value.toLowerCase() + return value.toLowerCase(); }, - { toClassOnly: true }, - ) + { toClassOnly: true } + ); } /** @@ -130,17 +123,15 @@ export function ToLowerCase(): PropertyDecorator { */ export function ToUpperCase(): PropertyDecorator { return Transform( - (params) => { - const value = params.value as string[] | string + params => { + const value = params.value as string[] | string; - if (!value) - return + if (!value) return; - if (isArray(value)) - return value.map(v => v.toUpperCase()) + if (isArray(value)) return value.map(v => v.toUpperCase()); - return value.toUpperCase() + return value.toUpperCase(); }, - { toClassOnly: true }, - ) + { toClassOnly: true } + ); } diff --git a/src/common/dto/cursor.dto.ts b/src/common/dto/cursor.dto.ts index 50c816c..59e7912 100644 --- a/src/common/dto/cursor.dto.ts +++ b/src/common/dto/cursor.dto.ts @@ -1,6 +1,6 @@ -import { ApiProperty } from '@nestjs/swagger' -import { Expose, Transform } from 'class-transformer' -import { IsInt, IsOptional, Max, Min } from 'class-validator' +import { ApiProperty } from '@nestjs/swagger'; +import { Expose, Transform } from 'class-transformer'; +import { IsInt, IsOptional, Max, Min } from 'class-validator'; export class CursorDto { @ApiProperty({ minimum: 0, default: 0 }) @@ -11,7 +11,7 @@ export class CursorDto { @Transform(({ value: val }) => (val ? Number.parseInt(val) : 0), { toClassOnly: true, }) - cursor?: number + cursor?: number; @ApiProperty({ minimum: 1, maximum: 100, default: 10 }) @Min(1) @@ -22,5 +22,5 @@ export class CursorDto { @Transform(({ value: val }) => (val ? Number.parseInt(val) : 10), { toClassOnly: true, }) - limit?: number + limit?: number; } diff --git a/src/common/dto/delete.dto.ts b/src/common/dto/delete.dto.ts index 5157aae..6f87b9e 100644 --- a/src/common/dto/delete.dto.ts +++ b/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/src/common/dto/id.dto.ts b/src/common/dto/id.dto.ts index 65898b1..271bece 100644 --- a/src/common/dto/id.dto.ts +++ b/src/common/dto/id.dto.ts @@ -1,6 +1,6 @@ -import { IsNumber } from 'class-validator' +import { IsNumber } from 'class-validator'; export class IdDto { @IsNumber() - id: number + id: number; } diff --git a/src/common/dto/pager.dto.ts b/src/common/dto/pager.dto.ts index 95abdaf..2467702 100644 --- a/src/common/dto/pager.dto.ts +++ b/src/common/dto/pager.dto.ts @@ -1,6 +1,6 @@ -import { ApiProperty } from '@nestjs/swagger' -import { Expose, Transform } from 'class-transformer' -import { Allow, IsEnum, IsInt, IsOptional, IsString, Max, Min } from 'class-validator' +import { ApiProperty } from '@nestjs/swagger'; +import { Expose, Transform } from 'class-transformer'; +import { Allow, IsEnum, IsInt, IsOptional, IsString, Max, Min } from 'class-validator'; export enum Order { ASC = 'ASC', @@ -16,7 +16,7 @@ export class PagerDto { @Transform(({ value: val }) => (val ? Number.parseInt(val) : 1), { toClassOnly: true, }) - page?: number + page?: number; @ApiProperty({ minimum: 1, maximum: 100, default: 10 }) @Min(1) @@ -27,19 +27,19 @@ export class PagerDto { @Transform(({ value: val }) => (val ? Number.parseInt(val) : 10), { toClassOnly: true, }) - pageSize?: number + pageSize?: number; @ApiProperty() @IsString() @IsOptional() - field?: string // | keyof T + field?: string; // | keyof T @ApiProperty({ enum: Order }) @IsEnum(Order) @IsOptional() @Transform(({ value }) => (value === 'asc' ? Order.ASC : Order.DESC)) - order?: Order + order?: Order; @Allow() - _t?: number + _t?: number; } diff --git a/src/common/entity/common.entity.ts b/src/common/entity/common.entity.ts index b6a9e32..b726edf 100644 --- a/src/common/entity/common.entity.ts +++ b/src/common/entity/common.entity.ts @@ -1,5 +1,5 @@ -import { ApiHideProperty, ApiProperty } from '@nestjs/swagger' -import { Exclude } from 'class-transformer' +import { ApiHideProperty, ApiProperty } from '@nestjs/swagger'; +import { Exclude } from 'class-transformer'; import { BaseEntity, Column, @@ -7,7 +7,7 @@ import { PrimaryGeneratedColumn, UpdateDateColumn, VirtualColumn, -} from 'typeorm' +} from 'typeorm'; // 如果觉得前端转换时间太麻烦,并且不考虑通用性的话,可以在服务端进行转换,eg: @UpdateDateColumn({ name: 'updated_at', transformer }) // const transformer: ValueTransformer = { @@ -21,25 +21,25 @@ import { export abstract class CommonEntity extends BaseEntity { @PrimaryGeneratedColumn() - id: number + id: number; @CreateDateColumn({ name: 'created_at' }) - createdAt: Date + createdAt: Date; @UpdateDateColumn({ name: 'updated_at' }) - updatedAt: Date + updatedAt: Date; } export abstract class CompleteEntity extends CommonEntity { @ApiHideProperty() @Exclude() @Column({ name: 'create_by', update: false, comment: '创建者' }) - createBy: number + createBy: number; @ApiHideProperty() @Exclude() @Column({ name: 'update_by', comment: '更新者' }) - updateBy: number + updateBy: number; /** * 不会保存到数据库中的虚拟列,数据量大时可能会有性能问题,有性能要求请考虑在 service 层手动实现 @@ -47,9 +47,9 @@ export abstract class CompleteEntity extends CommonEntity { */ @ApiProperty({ description: '创建者' }) @VirtualColumn({ query: alias => `SELECT username FROM sys_user WHERE id = ${alias}.create_by` }) - creator: string + creator: string; @ApiProperty({ description: '更新者' }) @VirtualColumn({ query: alias => `SELECT username FROM sys_user WHERE id = ${alias}.update_by` }) - updater: string + updater: string; } diff --git a/src/common/exceptions/biz.exception.ts b/src/common/exceptions/biz.exception.ts index f33ee3a..9b51262 100644 --- a/src/common/exceptions/biz.exception.ts +++ b/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 @@ -14,27 +14,27 @@ export class BusinessException extends HttpException { code: RESPONSE_SUCCESS_CODE, message: error, }), - HttpStatus.OK, - ) - this.errorCode = RESPONSE_SUCCESS_CODE - return + HttpStatus.OK + ); + this.errorCode = RESPONSE_SUCCESS_CODE; + return; } - const [code, message] = error.split(':') + const [code, message] = error.split(':'); super( HttpException.createBody({ code, message, }), - HttpStatus.OK, - ) + 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/src/common/exceptions/not-found.exception.ts b/src/common/exceptions/not-found.exception.ts index c6cb536..95b034d 100644 --- a/src/common/exceptions/not-found.exception.ts +++ b/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/src/common/exceptions/socket.exception.ts b/src/common/exceptions/socket.exception.ts index e790844..c25de02 100644 --- a/src/common/exceptions/socket.exception.ts +++ b/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/src/common/filters/any-exception.filter.ts b/src/common/filters/any-exception.filter.ts index 8bf04cd..f31ab40 100644 --- a/src/common/filters/any-exception.filter.ts +++ b/src/common/filters/any-exception.filter.ts @@ -5,85 +5,76 @@ 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'; interface 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 + const status = + exception instanceof HttpException ? exception.getStatus() - : (exception as myError)?.status - || (exception as myError)?.statusCode - || HttpStatus.INTERNAL_SERVER_ERROR + : (exception as myError)?.status || + (exception as myError)?.statusCode || + HttpStatus.INTERNAL_SERVER_ERROR; - let message - = (exception as any)?.response?.message - || (exception as myError)?.message - || `${exception}` + let message = + (exception as any)?.response?.message || (exception as myError)?.message || `${exception}`; // 系统内部错误时 - if ( - status === HttpStatus.INTERNAL_SERVER_ERROR - && !(exception instanceof BusinessException) - ) { - Logger.error(exception, undefined, 'Catch') + if (status === HttpStatus.INTERNAL_SERVER_ERROR && !(exception instanceof BusinessException)) { + Logger.error(exception, undefined, 'Catch'); // 生产环境下隐藏错误信息 - if (!isDev) - message = ErrorEnum.SERVER_ERROR?.split(':')[1] - } - else { - this.logger.warn( - `错误信息:(${status}) ${message} Path: ${decodeURI(url)}`, - ) + if (!isDev) message = ErrorEnum.SERVER_ERROR?.split(':')[1]; + } else { + this.logger.warn(`错误信息:(${status}) ${message} Path: ${decodeURI(url)}`); } - const apiErrorCode: number - = exception instanceof BusinessException ? exception.getErrorCode() : status + const apiErrorCode: number = + 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) - }) + process.on('unhandledRejection', reason => { + console.error('unhandledRejection: ', reason); + }); - process.on('uncaughtException', (err) => { - console.error('uncaughtException: ', err) - }) + process.on('uncaughtException', err => { + console.error('uncaughtException: ', err); + }); } } diff --git a/src/common/interceptors/idempotence.interceptor.ts b/src/common/interceptors/idempotence.interceptor.ts index a1f11d3..6fab029 100644 --- a/src/common/interceptors/idempotence.interceptor.ts +++ b/src/common/interceptors/idempotence.interceptor.ts @@ -1,76 +1,69 @@ -import type { - CallHandler, - ExecutionContext, - NestInterceptor, -} from '@nestjs/common' +import type { CallHandler, ExecutionContext, NestInterceptor } from '@nestjs/common'; + +import { ConflictException, Injectable, SetMetadata } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import type { FastifyRequest } from 'fastify'; +import { catchError, tap } from 'rxjs'; + +import { CacheService } from '~/shared/redis/cache.service'; +import { hashString } from '~/utils'; +import { getIp } from '~/utils/ip.util'; +import { getRedisKey } from '~/utils/redis.util'; import { - ConflictException, - Injectable, - SetMetadata, -} from '@nestjs/common' -import { Reflector } from '@nestjs/core' -import type { FastifyRequest } from 'fastify' -import { catchError, tap } from 'rxjs' + HTTP_IDEMPOTENCE_KEY, + HTTP_IDEMPOTENCE_OPTIONS, +} from '../decorators/idempotence.decorator'; -import { CacheService } from '~/shared/redis/cache.service' -import { hashString } from '~/utils' -import { getIp } from '~/utils/ip.util' -import { getRedisKey } from '~/utils/redis.util' - -import { HTTP_IDEMPOTENCE_KEY, HTTP_IDEMPOTENCE_OPTIONS } from '../decorators/idempotence.decorator' - -const IdempotenceHeaderKey = 'x-idempotence' +const IdempotenceHeaderKey = 'x-idempotence'; export interface IdempotenceOption { - errorMessage?: string - pendingMessage?: string + errorMessage?: string; + pendingMessage?: string; /** * 如果重复请求的话,手动处理异常 */ - handler?: (req: FastifyRequest) => any + handler?: (req: FastifyRequest) => any; /** * 记录重复请求的时间 * @default 60 */ - expired?: number + expired?: number; /** * 如果 header 没有幂等 key,根据 request 生成 key,如何生成这个 key 的方法 */ - generateKey?: (req: FastifyRequest) => string + generateKey?: (req: FastifyRequest) => string; /** * 仅读取 header 的 key,不自动生成 * @default false */ - disableGenerateKey?: boolean + disableGenerateKey?: boolean; } @Injectable() export class IdempotenceInterceptor implements NestInterceptor { constructor( private readonly reflector: Reflector, - private readonly cacheService: CacheService, + private readonly cacheService: CacheService ) {} async intercept(context: ExecutionContext, next: CallHandler) { - const request = context.switchToHttp().getRequest() + const request = context.switchToHttp().getRequest(); // skip Get 请求 - if (request.method.toUpperCase() === 'GET') - return next.handle() + if (request.method.toUpperCase() === 'GET') return next.handle(); - const handler = context.getHandler() + const handler = context.getHandler(); const options: IdempotenceOption | undefined = this.reflector.get( HTTP_IDEMPOTENCE_OPTIONS, - handler, - ) + handler + ); - if (!options) - return next.handle() + if (!options) return next.handle(); const { errorMessage = '相同请求成功后在 60 秒内只能发送一次', @@ -78,71 +71,64 @@ export class IdempotenceInterceptor implements NestInterceptor { handler: errorHandler, expired = 60, disableGenerateKey = false, - } = options - const redis = this.cacheService.getClient() + } = options; + const redis = this.cacheService.getClient(); - const idempotence = request.headers[IdempotenceHeaderKey] as string + const idempotence = request.headers[IdempotenceHeaderKey] as string; const key = disableGenerateKey ? undefined : options.generateKey ? options.generateKey(request) - : this.generateKey(request) + : this.generateKey(request); - const idempotenceKey - = !!(idempotence || key) && getRedisKey(`idempotence:${idempotence || key}`) + const idempotenceKey = + !!(idempotence || key) && getRedisKey(`idempotence:${idempotence || key}`); - SetMetadata(HTTP_IDEMPOTENCE_KEY, idempotenceKey)(handler) + SetMetadata(HTTP_IDEMPOTENCE_KEY, idempotenceKey)(handler); if (idempotenceKey) { - const resultValue: '0' | '1' | null = (await redis.get( - idempotenceKey, - )) as any + const resultValue: '0' | '1' | null = (await redis.get(idempotenceKey)) as any; if (resultValue !== null) { - if (errorHandler) - return await errorHandler(request) + if (errorHandler) return await errorHandler(request); const message = { 1: errorMessage, 0: pendingMessage, - }[resultValue] - throw new ConflictException(message) - } - else { - await redis.set(idempotenceKey, '0', 'EX', expired) + }[resultValue]; + throw new ConflictException(message); + } else { + await redis.set(idempotenceKey, '0', 'EX', expired); } } return next.handle().pipe( tap(async () => { - idempotenceKey && (await redis.set(idempotenceKey, '1', 'KEEPTTL')) + idempotenceKey && (await redis.set(idempotenceKey, '1', 'KEEPTTL')); }), - catchError(async (err) => { - if (idempotenceKey) - await redis.del(idempotenceKey) + catchError(async err => { + if (idempotenceKey) await redis.del(idempotenceKey); - throw err - }), - ) + throw err; + }) + ); } private generateKey(req: FastifyRequest) { - const { body, params, query = {}, headers, url } = req + const { body, params, query = {}, headers, url } = req; - const obj = { body, url, params, query } as any + const obj = { body, url, params, query } as any; - const uuid = headers['x-uuid'] + const uuid = headers['x-uuid']; if (uuid) { - obj.uuid = uuid - } - else { - const ua = headers['user-agent'] - const ip = getIp(req) + obj.uuid = uuid; + } else { + const ua = headers['user-agent']; + const ip = getIp(req); - if (!ua && !ip) - return undefined + if (!ua && !ip) return undefined; - Object.assign(obj, { ua, ip }) + Object.assign(obj, { ua, ip }); } - return hashString(JSON.stringify(obj)) + return hashString(JSON.stringify(obj)); } } diff --git a/src/common/interceptors/logging.interceptor.ts b/src/common/interceptors/logging.interceptor.ts index de54623..d9595f7 100644 --- a/src/common/interceptors/logging.interceptor.ts +++ b/src/common/interceptors/logging.interceptor.ts @@ -1,35 +1,24 @@ -import { - CallHandler, - ExecutionContext, - Injectable, - Logger, - NestInterceptor, -} from '@nestjs/common' -import { Observable, tap } from 'rxjs' +import { CallHandler, ExecutionContext, Injectable, Logger, NestInterceptor } 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}` - const isSse = request.headers.accept === 'text/event-stream' - this.logger.debug(`+++ 请求:${content}`) - const now = Date.now() + intercept(context: ExecutionContext, next: CallHandler): Observable { + const call$ = next.handle(); + const request = context.switchToHttp().getRequest(); + const content = `${request.method} -> ${request.url}`; + const isSse = request.headers.accept === 'text/event-stream'; + this.logger.debug(`+++ 请求:${content}`); + const now = Date.now(); return call$.pipe( tap(() => { - if (isSse) - return + if (isSse) return; - this.logger.debug(`--- 响应:${content}${` +${Date.now() - now}ms`}`) - }, - ), - ) + this.logger.debug(`--- 响应:${content}${` +${Date.now() - now}ms`}`); + }) + ); } } diff --git a/src/common/interceptors/timeout.interceptor.ts b/src/common/interceptors/timeout.interceptor.ts index 8508c58..464d53c 100644 --- a/src/common/interceptors/timeout.interceptor.ts +++ b/src/common/interceptors/timeout.interceptor.ts @@ -4,9 +4,9 @@ import { Injectable, NestInterceptor, RequestTimeoutException, -} from '@nestjs/common' -import { Observable, TimeoutError, throwError } from 'rxjs' -import { catchError, timeout } from 'rxjs/operators' +} from '@nestjs/common'; +import { Observable, TimeoutError, throwError } from 'rxjs'; +import { catchError, timeout } from 'rxjs/operators'; @Injectable() export class TimeoutInterceptor implements NestInterceptor { @@ -15,12 +15,11 @@ export class TimeoutInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable { return next.handle().pipe( timeout(this.time), - catchError((err) => { - if (err instanceof TimeoutError) - return throwError(new RequestTimeoutException('请求超时')) + catchError(err => { + if (err instanceof TimeoutError) return throwError(new RequestTimeoutException('请求超时')); - return throwError(err) - }), - ) + return throwError(err); + }) + ); } } diff --git a/src/common/interceptors/transform.interceptor.ts b/src/common/interceptors/transform.interceptor.ts index 4b59fbe..8ba5c95 100644 --- a/src/common/interceptors/transform.interceptor.ts +++ b/src/common/interceptors/transform.interceptor.ts @@ -4,14 +4,14 @@ import { HttpStatus, Injectable, 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 { BYPASS_KEY } from '../decorators/bypass.decorator' +import { BYPASS_KEY } from '../decorators/bypass.decorator'; /** * 统一处理返回接口结果,如果不需要则添加 @Bypass 装饰器 @@ -20,27 +20,20 @@ import { BYPASS_KEY } from '../decorators/bypass.decorator' export class TransformInterceptor implements NestInterceptor { constructor(private readonly reflector: Reflector) {} - intercept( - context: ExecutionContext, - next: CallHandler, - ): Observable { - const bypass = this.reflector.get( - BYPASS_KEY, - context.getHandler(), - ) + intercept(context: ExecutionContext, next: CallHandler): Observable { + const bypass = this.reflector.get(BYPASS_KEY, context.getHandler()); - if (bypass) - return next.handle() + if (bypass) return next.handle(); return next.handle().pipe( - map((data) => { + map(data => { // if (typeof data === 'undefined') { // context.switchToHttp().getResponse().status(HttpStatus.NO_CONTENT); // return data; // } - return new ResOp(HttpStatus.OK, data ?? null) - }), - ) + return new ResOp(HttpStatus.OK, data ?? null); + }) + ); } } diff --git a/src/common/model/response.model.ts b/src/common/model/response.model.ts index 0336456..b3f6330 100644 --- a/src/common/model/response.model.ts +++ b/src/common/model/response.model.ts @@ -1,42 +1,39 @@ -import { ApiProperty } from '@nestjs/swagger' +import { ApiProperty } from '@nestjs/swagger'; -import { - RESPONSE_SUCCESS_CODE, - RESPONSE_SUCCESS_MSG, -} from '~/constants/response.constant' +import { RESPONSE_SUCCESS_CODE, RESPONSE_SUCCESS_MSG } 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/src/common/pipes/parse-int.pipe.ts b/src/common/pipes/parse-int.pipe.ts index 514d67c..c6d7227 100644 --- a/src/common/pipes/parse-int.pipe.ts +++ b/src/common/pipes/parse-int.pipe.ts @@ -1,18 +1,12 @@ -import { - ArgumentMetadata, - BadRequestException, - Injectable, - PipeTransform, -} from '@nestjs/common' +import { ArgumentMetadata, BadRequestException, Injectable, PipeTransform } from '@nestjs/common'; @Injectable() export class ParseIntPipe implements PipeTransform { transform(value: string, metadata: ArgumentMetadata): number { - const val = Number.parseInt(value, 10) + const val = Number.parseInt(value, 10); - if (Number.isNaN(val)) - throw new BadRequestException('id validation failed') + if (Number.isNaN(val)) throw new BadRequestException('id validation failed'); - return val + return val; } } diff --git a/src/config/app.config.ts b/src/config/app.config.ts index 25f2a14..072be51 100644 --- a/src/config/app.config.ts +++ b/src/config/app.config.ts @@ -1,8 +1,8 @@ -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 appRegToken = 'app' +export const appRegToken = 'app'; export const AppConfig = registerAs(appRegToken, () => ({ name: env('APP_NAME'), @@ -15,6 +15,6 @@ export const AppConfig = registerAs(appRegToken, () => ({ level: env('LOGGER_LEVEL'), maxFiles: envNumber('LOGGER_MAX_FILES'), }, -})) +})); -export type IAppConfig = ConfigType +export type IAppConfig = ConfigType; diff --git a/src/config/database.config.ts b/src/config/database.config.ts index 2d1b34f..2f9f706 100644 --- a/src/config/database.config.ts +++ b/src/config/database.config.ts @@ -1,16 +1,16 @@ -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}` }); // 当前通过 npm scripts 执行的命令 -const currentScript = process.env.npm_lifecycle_event +const currentScript = process.env.npm_lifecycle_event; const dataSourceOptions: DataSourceOptions = { type: 'mysql', @@ -25,16 +25,13 @@ const dataSourceOptions: DataSourceOptions = { entities: ['dist/modules/**/*.entity{.ts,.js}'], migrations: ['dist/migrations/*{.ts,.js}'], subscribers: ['dist/modules/**/*.subscriber{.ts,.js}'], -} -export const dbRegToken = 'database' +}; +export const dbRegToken = 'database'; -export const DatabaseConfig = registerAs( - dbRegToken, - (): DataSourceOptions => dataSourceOptions, -) +export const DatabaseConfig = registerAs(dbRegToken, (): 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/src/config/index.ts b/src/config/index.ts index 7cca557..4154c0a 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -1,30 +1,30 @@ -import { AppConfig, IAppConfig, appRegToken } from './app.config' -import { DatabaseConfig, IDatabaseConfig, dbRegToken } from './database.config' -import { IMailerConfig, MailerConfig, mailerRegToken } from './mailer.config' -import { IOssConfig, OssConfig, ossRegToken } from './oss.config' -import { IRedisConfig, RedisConfig, redisRegToken } from './redis.config' -import { ISecurityConfig, SecurityConfig, securityRegToken } from './security.config' -import { ISwaggerConfig, SwaggerConfig, swaggerRegToken } from './swagger.config' +import { AppConfig, IAppConfig, appRegToken } from './app.config'; +import { DatabaseConfig, IDatabaseConfig, dbRegToken } from './database.config'; +import { IMailerConfig, MailerConfig, mailerRegToken } from './mailer.config'; +import { IOssConfig, OssConfig, ossRegToken } from './oss.config'; +import { IRedisConfig, RedisConfig, redisRegToken } from './redis.config'; +import { ISecurityConfig, SecurityConfig, securityRegToken } from './security.config'; +import { ISwaggerConfig, SwaggerConfig, swaggerRegToken } from './swagger.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 './oss.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 './oss.config'; export interface AllConfigType { - [appRegToken]: IAppConfig - [dbRegToken]: IDatabaseConfig - [mailerRegToken]: IMailerConfig - [redisRegToken]: IRedisConfig - [securityRegToken]: ISecurityConfig - [swaggerRegToken]: ISwaggerConfig - [ossRegToken]: IOssConfig + [appRegToken]: IAppConfig; + [dbRegToken]: IDatabaseConfig; + [mailerRegToken]: IMailerConfig; + [redisRegToken]: IRedisConfig; + [securityRegToken]: ISecurityConfig; + [swaggerRegToken]: ISwaggerConfig; + [ossRegToken]: IOssConfig; } -export type ConfigKeyPaths = RecordNamePaths +export type ConfigKeyPaths = RecordNamePaths; export default { AppConfig, @@ -34,4 +34,4 @@ export default { RedisConfig, SecurityConfig, SwaggerConfig, -} +}; diff --git a/src/config/mailer.config.ts b/src/config/mailer.config.ts index 27763d1..2f60052 100644 --- a/src/config/mailer.config.ts +++ b/src/config/mailer.config.ts @@ -1,8 +1,8 @@ -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 mailerRegToken = 'mailer' +export const mailerRegToken = 'mailer'; export const MailerConfig = registerAs(mailerRegToken, () => ({ host: env('SMTP_HOST'), @@ -13,6 +13,6 @@ export const MailerConfig = registerAs(mailerRegToken, () => ({ user: env('SMTP_USER'), pass: env('SMTP_PASS'), }, -})) +})); -export type IMailerConfig = ConfigType +export type IMailerConfig = ConfigType; diff --git a/src/config/oss.config.ts b/src/config/oss.config.ts index ae1f563..0f00e42 100644 --- a/src/config/oss.config.ts +++ b/src/config/oss.config.ts @@ -1,24 +1,24 @@ -import { ConfigType, registerAs } from '@nestjs/config' -import * as qiniu from 'qiniu' +import { ConfigType, registerAs } from '@nestjs/config'; +import * as qiniu from 'qiniu'; -import { env } from '~/global/env' +import { env } from '~/global/env'; function parseZone(zone: string) { switch (zone) { case 'Zone_as0': - return qiniu.zone.Zone_as0 + return qiniu.zone.Zone_as0; case 'Zone_na0': - return qiniu.zone.Zone_na0 + return qiniu.zone.Zone_na0; case 'Zone_z0': - return qiniu.zone.Zone_z0 + return qiniu.zone.Zone_z0; case 'Zone_z1': - return qiniu.zone.Zone_z1 + return qiniu.zone.Zone_z1; case 'Zone_z2': - return qiniu.zone.Zone_z2 + return qiniu.zone.Zone_z2; } } -export const ossRegToken = 'oss' +export const ossRegToken = 'oss'; export const OssConfig = registerAs(ossRegToken, () => ({ accessKey: env('OSS_ACCESSKEY'), @@ -27,6 +27,6 @@ export const OssConfig = registerAs(ossRegToken, () => ({ bucket: env('OSS_BUCKET'), zone: parseZone(env('OSS_ZONE') || 'Zone_z2'), access: (env('OSS_ACCESS_TYPE') as any) || 'public', -})) +})); -export type IOssConfig = ConfigType +export type IOssConfig = ConfigType; diff --git a/src/config/redis.config.ts b/src/config/redis.config.ts index 749c772..5cbdbff 100644 --- a/src/config/redis.config.ts +++ b/src/config/redis.config.ts @@ -1,14 +1,14 @@ -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 redisRegToken = 'redis' +export const redisRegToken = 'redis'; export const RedisConfig = registerAs(redisRegToken, () => ({ 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/src/config/security.config.ts b/src/config/security.config.ts index 024cccb..f69ac11 100644 --- a/src/config/security.config.ts +++ b/src/config/security.config.ts @@ -1,14 +1,14 @@ -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 securityRegToken = 'security' +export const securityRegToken = 'security'; export const SecurityConfig = registerAs(securityRegToken, () => ({ 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/src/config/swagger.config.ts b/src/config/swagger.config.ts index 0424fcf..c29a9c3 100644 --- a/src/config/swagger.config.ts +++ b/src/config/swagger.config.ts @@ -1,12 +1,12 @@ -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 swaggerRegToken = 'swagger' +export const swaggerRegToken = 'swagger'; export const SwaggerConfig = registerAs(swaggerRegToken, () => ({ enable: envBoolean('SWAGGER_ENABLE'), path: env('SWAGGER_PATH'), -})) +})); -export type ISwaggerConfig = ConfigType +export type ISwaggerConfig = ConfigType; diff --git a/src/constants/cache.constant.ts b/src/constants/cache.constant.ts index c4852b8..34d9b26 100644 --- a/src/constants/cache.constant.ts +++ b/src/constants/cache.constant.ts @@ -5,4 +5,4 @@ export enum RedisKeys { AUTH_PERM_PREFIX = 'auth:permission:', AUTH_PASSWORD_V_PREFIX = 'auth:passwordVersion:', } -export const API_CACHE_PREFIX = 'api-cache:' +export const API_CACHE_PREFIX = 'api-cache:'; diff --git a/src/constants/oss.constant.ts b/src/constants/oss.constant.ts index a56336e..ceecb80 100644 --- a/src/constants/oss.constant.ts +++ b/src/constants/oss.constant.ts @@ -1,8 +1,8 @@ -export const OSS_CONFIG = 'admin_module:qiniu_config' -export const OSS_API = 'http://api.qiniu.com' +export const OSS_CONFIG = 'admin_module:qiniu_config'; +export const OSS_API = 'http://api.qiniu.com'; // 目录分隔符 -export const NETDISK_DELIMITER = '/' -export const NETDISK_LIMIT = 100 -export const NETDISK_HANDLE_MAX_ITEM = 1000 -export const NETDISK_COPY_SUFFIX = '的副本' +export const NETDISK_DELIMITER = '/'; +export const NETDISK_LIMIT = 100; +export const NETDISK_HANDLE_MAX_ITEM = 1000; +export const NETDISK_COPY_SUFFIX = '的副本'; diff --git a/src/constants/response.constant.ts b/src/constants/response.constant.ts index 229f70f..5b5395b 100644 --- a/src/constants/response.constant.ts +++ b/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/src/constants/system.constant.ts b/src/constants/system.constant.ts index eef03a5..d1e191c 100644 --- a/src/constants/system.constant.ts +++ b/src/constants/system.constant.ts @@ -1,6 +1,6 @@ -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'; /** 超级管理员用户 id */ -export const ROOT_USER_ID = 1 +export const ROOT_USER_ID = 1; /** 超级管理员角色 id */ -export const ROOT_ROLE_ID = 1 +export const ROOT_ROLE_ID = 1; diff --git a/src/global/env.ts b/src/global/env.ts index 8f7b82b..4e283c0 100644 --- a/src/global/env.ts +++ b/src/global/env.ts @@ -1,18 +1,18 @@ -import cluster from 'node:cluster' +import cluster from 'node:cluster'; -export const isMainCluster - = process.env.NODE_APP_INSTANCE && Number.parseInt(process.env.NODE_APP_INSTANCE) === 0 -export const isMainProcess = cluster.isPrimary || isMainCluster +export const isMainCluster = + process.env.NODE_APP_INSTANCE && Number.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; /** * 格式化环境变量 @@ -20,43 +20,43 @@ export type BaseType = boolean | number | string | undefined | null * @param defaultValue 默认值 * @param callback 格式化函数 */ -function fromatValue(key: string, defaultValue: T, callback?: (value: string) => T): T { - const value: string | undefined = process.env[key] - if (typeof value === 'undefined') - return defaultValue +function fromatValue( + key: string, + defaultValue: T, + callback?: (value: string) => T +): T { + const value: string | undefined = process.env[key]; + if (typeof value === 'undefined') return defaultValue; - if (!callback) - return value as unknown as T + if (!callback) return value as unknown as T; - return callback(value) + return callback(value); } export function env(key: string, defaultValue: string = '') { - return fromatValue(key, defaultValue) + return fromatValue(key, defaultValue); } export function envString(key: string, defaultValue: string = '') { - return fromatValue(key, defaultValue) + return fromatValue(key, defaultValue); } export function envNumber(key: string, defaultValue: number = 0) { - return fromatValue(key, defaultValue, (value) => { + return fromatValue(key, defaultValue, value => { try { - return Number(value) + return Number(value); + } catch { + throw new Error(`${key} environment variable is not a number`); } - catch { - throw new Error(`${key} environment variable is not a number`) - } - }) + }); } export function envBoolean(key: string, defaultValue: boolean = false) { - return fromatValue(key, defaultValue, (value) => { + return 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`); } - catch { - throw new Error(`${key} environment variable is not a boolean`) - } - }) + }); } diff --git a/src/helper/catchError.ts b/src/helper/catchError.ts index d21789c..78b8502 100644 --- a/src/helper/catchError.ts +++ b/src/helper/catchError.ts @@ -1,5 +1,5 @@ export function catchError() { process.on('unhandledRejection', (reason, p) => { - console.log('Promise: ', p, 'Reason: ', reason) - }) + console.log('Promise: ', p, 'Reason: ', reason); + }); } diff --git a/src/helper/crud/base.service.ts b/src/helper/crud/base.service.ts index daa67a4..41c526b 100644 --- a/src/helper/crud/base.service.ts +++ b/src/helper/crud/base.service.ts @@ -1,40 +1,35 @@ -import { NotFoundException } from '@nestjs/common' -import { ObjectLiteral, Repository } from 'typeorm' +import { NotFoundException } from '@nestjs/common'; +import { ObjectLiteral, Repository } from 'typeorm'; -import { PagerDto } from '~/common/dto/pager.dto' +import { PagerDto } from '~/common/dto/pager.dto'; -import { paginate } from '../paginate' -import { Pagination } from '../paginate/pagination' +import { paginate } from '../paginate'; +import { Pagination } from '../paginate/pagination'; export class BaseService = Repository> { - constructor(private repository: R) { - } + constructor(private repository: R) {} - async list({ - page, - pageSize, - }: PagerDto): Promise> { - return paginate(this.repository, { page, pageSize }) + async list({ page, pageSize }: PagerDto): Promise> { + return paginate(this.repository, { page, pageSize }); } async findOne(id: number): Promise { - const item = await this.repository.createQueryBuilder().where({ id }).getOne() - if (!item) - throw new NotFoundException('未找到该记录') + const item = await this.repository.createQueryBuilder().where({ id }).getOne(); + if (!item) throw new NotFoundException('未找到该记录'); - return item + return item; } async create(dto: any): Promise { - return await this.repository.save(dto) + return await this.repository.save(dto); } async update(id: number, dto: any): Promise { - await this.repository.update(id, dto) + await this.repository.update(id, dto); } async delete(id: number): Promise { - const item = await this.findOne(id) - await this.repository.remove(item) + const item = await this.findOne(id); + await this.repository.remove(item); } } diff --git a/src/helper/crud/crud.factory.ts b/src/helper/crud/crud.factory.ts index 7ae1fc8..67b0e6d 100644 --- a/src/helper/crud/crud.factory.ts +++ b/src/helper/crud/crud.factory.ts @@ -1,81 +1,80 @@ -import type { Type } from '@nestjs/common' +import type { Type } from '@nestjs/common'; -import { - Body, - Controller, - Delete, - Get, - Patch, - Post, - Put, - Query, -} from '@nestjs/common' -import { ApiBody, IntersectionType, PartialType } from '@nestjs/swagger' -import pluralize from 'pluralize' +import { Body, Controller, Delete, Get, Patch, Post, Put, Query } from '@nestjs/common'; +import { ApiBody, IntersectionType, PartialType } from '@nestjs/swagger'; +import pluralize from 'pluralize'; -import { ApiResult } from '~/common/decorators/api-result.decorator' -import { IdParam } from '~/common/decorators/id-param.decorator' -import { PagerDto } from '~/common/dto/pager.dto' +import { ApiResult } from '~/common/decorators/api-result.decorator'; +import { IdParam } from '~/common/decorators/id-param.decorator'; +import { PagerDto } from '~/common/dto/pager.dto'; -import { BaseService } from './base.service' +import { BaseService } from './base.service'; -export function BaseCrudFactory< - E extends new (...args: any[]) => any, ->({ entity, dto, permissions }: { entity: E, dto?: Type, permissions?: Record }): Type { - const prefix = entity.name.toLowerCase().replace(/entity$/, '') - const pluralizeName = pluralize(prefix) as string +export function BaseCrudFactory any>({ + entity, + dto, + permissions, +}: { + entity: E; + dto?: Type; + permissions?: Record; +}): Type { + const prefix = entity.name.toLowerCase().replace(/entity$/, ''); + const pluralizeName = pluralize(prefix) as string; - dto = dto ?? class extends entity {} + dto = dto ?? class extends entity {}; class Dto extends dto {} class UpdateDto extends PartialType(Dto) {} class QueryDto extends IntersectionType(PagerDto, PartialType(Dto)) {} - permissions = permissions ?? { - LIST: `${prefix}:list`, - CREATE: `${prefix}:create`, - READ: `${prefix}:read`, - UPDATE: `${prefix}:update`, - DELETE: `${prefix}:delete`, - } as const + permissions = + permissions ?? + ({ + LIST: `${prefix}:list`, + CREATE: `${prefix}:create`, + READ: `${prefix}:read`, + UPDATE: `${prefix}:update`, + DELETE: `${prefix}:delete`, + } as const); @Controller(pluralizeName) class BaseController> { - constructor(private service: S) { } + constructor(private service: S) {} @Get() @ApiResult({ type: [entity], isPage: true }) async list(@Query() pager: QueryDto) { - return await this.service.list(pager) + return await this.service.list(pager); } @Get(':id') @ApiResult({ type: entity }) async get(@IdParam() id: number) { - return await this.service.findOne(id) + return await this.service.findOne(id); } @Post() @ApiBody({ type: dto }) async create(@Body() dto: Dto) { - return await this.service.create(dto) + return await this.service.create(dto); } @Put(':id') async update(@IdParam() id: number, @Body() dto: UpdateDto) { - return await this.service.update(id, dto) + return await this.service.update(id, dto); } @Patch(':id') async patch(@IdParam() id: number, @Body() dto: UpdateDto) { - await this.service.update(id, dto) + await this.service.update(id, dto); } @Delete(':id') async delete(@IdParam() id: number) { - await this.service.delete(id) + await this.service.delete(id); } } - return BaseController + return BaseController; } diff --git a/src/helper/genRedisKey.ts b/src/helper/genRedisKey.ts index 1dab9a3..4cbe485 100644 --- a/src/helper/genRedisKey.ts +++ b/src/helper/genRedisKey.ts @@ -1,19 +1,19 @@ -import { RedisKeys } from '~/constants/cache.constant' +import { RedisKeys } from '~/constants/cache.constant'; /** 生成验证码 redis key */ export function genCaptchaImgKey(val: string | number) { - return `${RedisKeys.CAPTCHA_IMG_PREFIX}${String(val)}` as const + return `${RedisKeys.CAPTCHA_IMG_PREFIX}${String(val)}` as const; } /** 生成 auth token redis key */ export function genAuthTokenKey(val: string | number) { - return `${RedisKeys.AUTH_TOKEN_PREFIX}${String(val)}` as const + return `${RedisKeys.AUTH_TOKEN_PREFIX}${String(val)}` as const; } /** 生成 auth permission redis key */ export function genAuthPermKey(val: string | number) { - return `${RedisKeys.AUTH_PERM_PREFIX}${String(val)}` as const + return `${RedisKeys.AUTH_PERM_PREFIX}${String(val)}` as const; } /** 生成 auth passwordVersion redis key */ export function genAuthPVKey(val: string | number) { - return `${RedisKeys.AUTH_PASSWORD_V_PREFIX}${String(val)}` as const + return `${RedisKeys.AUTH_PASSWORD_V_PREFIX}${String(val)}` as const; } diff --git a/src/helper/paginate/create-pagination.ts b/src/helper/paginate/create-pagination.ts index 5c1182f..589e416 100644 --- a/src/helper/paginate/create-pagination.ts +++ b/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,12 @@ 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 + const totalPages = totalItems !== undefined ? Math.ceil(totalItems / limit) : undefined; const meta: IPaginationMeta = { totalItems, @@ -21,7 +20,7 @@ export function createPaginationObject({ itemsPerPage: limit, totalPages, currentPage, - } + }; - return new Pagination(items, meta) + return new Pagination(items, meta); } diff --git a/src/helper/paginate/index.ts b/src/helper/paginate/index.ts index fa8b487..b50b8a8 100644 --- a/src/helper/paginate/index.ts +++ b/src/helper/paginate/index.ts @@ -4,33 +4,31 @@ 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 +function resolveOptions(options: IPaginationOptions): [number, number, PaginationTypeEnum] { + const { page, pageSize, paginationType } = options; return [ page || DEFAULT_PAGE, pageSize || DEFAULT_LIMIT, paginationType || PaginationTypeEnum.TAKE_AND_SKIP, - ] + ]; } async function paginateRepository( repository: Repository, options: IPaginationOptions, - searchOptions?: FindOptionsWhere | FindManyOptions, + searchOptions?: FindOptionsWhere | FindManyOptions ): Promise> { - const [page, limit] = resolveOptions(options) + const [page, limit] = resolveOptions(options); const promises: [Promise, Promise | undefined] = [ repository.find({ @@ -39,44 +37,43 @@ 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, + 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) - else - queryBuilder.limit(limit).offset((page - 1) * limit) + queryBuilder.take(limit).skip((page - 1) * limit); + else 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, + 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 @@ -84,36 +81,33 @@ 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, + options: IPaginationOptions ): Promise<[Pagination, Partial[]]> { - const [page, limit, paginationType] = resolveOptions(options) + const [page, limit, paginationType] = resolveOptions(options); - const promises: [ - Promise<{ entities: T[], raw: T[] }>, - Promise | undefined, - ] = [ + const promises: [Promise<{ entities: T[]; raw: T[] }>, Promise | undefined] = [ (paginationType === PaginationTypeEnum.LIMIT_AND_OFFSET ? queryBuilder.limit(limit).offset((page - 1) * limit) : 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({ @@ -123,25 +117,25 @@ export async function paginateRawAndEntities( limit, }), itemObject.raw, - ] + ]; } export async function paginate( repository: Repository, options: IPaginationOptions, - searchOptions?: FindOptionsWhere | FindManyOptions, -): Promise> + searchOptions?: FindOptionsWhere | FindManyOptions +): Promise>; export async function paginate( queryBuilder: SelectQueryBuilder, - options: IPaginationOptions, -): Promise> + options: IPaginationOptions +): Promise>; export async function paginate( repositoryOrQueryBuilder: Repository | SelectQueryBuilder, options: IPaginationOptions, - searchOptions?: FindOptionsWhere | FindManyOptions, + searchOptions?: FindOptionsWhere | FindManyOptions ) { return repositoryOrQueryBuilder instanceof Repository ? paginateRepository(repositoryOrQueryBuilder, options, searchOptions) - : paginateQueryBuilder(repositoryOrQueryBuilder, options) + : paginateQueryBuilder(repositoryOrQueryBuilder, options); } diff --git a/src/helper/paginate/interface.ts b/src/helper/paginate/interface.ts index e7c6ee2..d2cd7d9 100644 --- a/src/helper/paginate/interface.ts +++ b/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/src/helper/paginate/pagination.ts b/src/helper/paginate/pagination.ts index 5bcf90f..1d52aba 100644 --- a/src/helper/paginate/pagination.ts +++ b/src/helper/paginate/pagination.ts @@ -1,14 +1,11 @@ -import { ObjectLiteral } from 'typeorm' +import { ObjectLiteral } from 'typeorm'; -import { IPaginationMeta } from './interface' +import { IPaginationMeta } from './interface'; -export class Pagination< - PaginationObject, - T extends ObjectLiteral = IPaginationMeta, -> { +export class Pagination { constructor( public readonly items: PaginationObject[], - public readonly meta: T, + public readonly meta: T ) {} } diff --git a/src/main.ts b/src/main.ts index 4fd1732..2a174a4 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,56 +1,46 @@ -import cluster from 'node:cluster' -import path from 'node:path' +import cluster from 'node:cluster'; +import path from 'node:path'; -import { - HttpStatus, - Logger, - UnprocessableEntityException, - ValidationPipe, -} from '@nestjs/common' -import { ConfigService } from '@nestjs/config' -import { NestFactory } from '@nestjs/core' -import { NestFastifyApplication } from '@nestjs/platform-fastify' +import { HttpStatus, Logger, UnprocessableEntityException, ValidationPipe } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { NestFactory } from '@nestjs/core'; +import { NestFastifyApplication } from '@nestjs/platform-fastify'; -import { useContainer } from 'class-validator' +import { useContainer } from 'class-validator'; -import { AppModule } from './app.module' +import { AppModule } from './app.module'; -import { fastifyApp } from './common/adapters/fastify.adapter' -import { RedisIoAdapter } from './common/adapters/socket.adapter' -import { LoggingInterceptor } from './common/interceptors/logging.interceptor' -import type { ConfigKeyPaths } from './config' -import { isDev, isMainProcess } from './global/env' -import { setupSwagger } from './setup-swagger' -import { LoggerService } from './shared/logger/logger.service' +import { fastifyApp } from './common/adapters/fastify.adapter'; +import { RedisIoAdapter } from './common/adapters/socket.adapter'; +import { LoggingInterceptor } from './common/interceptors/logging.interceptor'; +import type { ConfigKeyPaths } from './config'; +import { isDev, isMainProcess } from './global/env'; +import { setupSwagger } from './setup-swagger'; +import { LoggerService } from './shared/logger/logger.service'; -declare const module: any +declare const module: any; async function bootstrap() { - const app = await NestFactory.create( - AppModule, - fastifyApp, - { - bufferLogs: true, - snapshot: true, - // forceCloseConnections: true, - }, - ) + const app = await NestFactory.create(AppModule, fastifyApp, { + bufferLogs: true, + snapshot: true, + // forceCloseConnections: true, + }); - const configService = app.get(ConfigService) + const configService = app.get(ConfigService); - const { port, globalPrefix } = configService.get('app', { infer: true }) + const { port, globalPrefix } = configService.get('app', { infer: true }); // class-validator 的 DTO 类中注入 nest 容器的依赖 (用于自定义验证器) - useContainer(app.select(AppModule), { fallbackOnErrors: true }) + useContainer(app.select(AppModule), { fallbackOnErrors: true }); - app.enableCors({ origin: '*', credentials: true }) - app.setGlobalPrefix(globalPrefix) - app.useStaticAssets({ root: path.join(__dirname, '..', 'public') }) + app.enableCors({ origin: '*', credentials: true }); + app.setGlobalPrefix(globalPrefix); + app.useStaticAssets({ root: path.join(__dirname, '..', 'public') }); // Starts listening for shutdown hooks - !isDev && app.enableShutdownHooks() + !isDev && app.enableShutdownHooks(); - if (isDev) - app.useGlobalInterceptors(new LoggingInterceptor()) + if (isDev) app.useGlobalInterceptors(new LoggingInterceptor()); app.useGlobalPipes( new ValidationPipe({ @@ -62,40 +52,38 @@ async function bootstrap() { stopAtFirstError: true, exceptionFactory: errors => new UnprocessableEntityException( - errors.map((e) => { - const rule = Object.keys(e.constraints!)[0] - const msg = e.constraints![rule] - return msg - })[0], + errors.map(e => { + const rule = Object.keys(e.constraints!)[0]; + const msg = e.constraints![rule]; + return msg; + })[0] ), - }), - ) + }) + ); - app.useWebSocketAdapter(new RedisIoAdapter(app)) + app.useWebSocketAdapter(new RedisIoAdapter(app)); - setupSwagger(app, configService) + setupSwagger(app, configService); await app.listen(port, '0.0.0.0', async () => { - app.useLogger(app.get(LoggerService)) - const url = await app.getUrl() - const { pid } = process - const env = cluster.isPrimary - const prefix = env ? 'P' : 'W' + app.useLogger(app.get(LoggerService)); + const url = await app.getUrl(); + const { pid } = process; + const env = cluster.isPrimary; + const prefix = env ? 'P' : 'W'; - if (!isMainProcess) - return + if (!isMainProcess) 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`) - }) + if (isDev) 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/src/migrations/1707996695540-initData.ts b/src/migrations/1707996695540-initData.ts index 75f758c..88a7bf6 100644 --- a/src/migrations/1707996695540-initData.ts +++ b/src/migrations/1707996695540-initData.ts @@ -1,15 +1,14 @@ -import fs from 'node:fs' -import path from 'node:path' +import fs from 'node:fs'; +import path from 'node:path'; -import { MigrationInterface, QueryRunner } from 'typeorm' +import { MigrationInterface, QueryRunner } from 'typeorm'; -const sql = fs.readFileSync(path.join(__dirname, '../../deploy/sql/hxoa.sql'), 'utf8') +const sql = fs.readFileSync(path.join(__dirname, '../../deploy/sql/hxoa.sql'), 'utf8'); export class InitData1707996695540 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(sql) + await queryRunner.query(sql); } - public async down(queryRunner: QueryRunner): Promise { - } + public async down(queryRunner: QueryRunner): Promise {} } diff --git a/src/modules/auth/auth.constant.ts b/src/modules/auth/auth.constant.ts index 08d48e6..0a9f744 100644 --- a/src/modules/auth/auth.constant.ts +++ b/src/modules/auth/auth.constant.ts @@ -1,10 +1,10 @@ -export const PUBLIC_KEY = '__public_key__' +export const PUBLIC_KEY = '__public_key__'; -export const PERMISSION_KEY = '__permission_key__' +export const PERMISSION_KEY = '__permission_key__'; -export const RESOURCE_KEY = '__resource_key__' +export const RESOURCE_KEY = '__resource_key__'; -export const ALLOW_ANON_KEY = '__allow_anon_permission_key__' +export const ALLOW_ANON_KEY = '__allow_anon_permission_key__'; export const AuthStrategy = { LOCAL: 'local', @@ -15,12 +15,12 @@ export const AuthStrategy = { GITHUB: 'github', GOOGLE: 'google', -} as const +} as const; export const Roles = { ADMIN: 'admin', USER: 'user', // GUEST: 'guest', -} as const +} as const; -export type Role = (typeof Roles)[keyof typeof Roles] +export type Role = (typeof Roles)[keyof typeof Roles]; diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index cb1bed7..b3270a4 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -1,17 +1,17 @@ -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 { CaptchaService } from './services/captcha.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 { CaptchaService } from './services/captcha.service'; @ApiTags('Auth - 认证模块') @UseGuards(LocalGuard) @@ -21,27 +21,25 @@ export class AuthController { constructor( private authService: AuthService, private userService: UserService, - private captchaService: CaptchaService, + private captchaService: CaptchaService ) {} @Post('login') @ApiOperation({ summary: '登录' }) @ApiResult({ type: LoginToken }) async login( - @Body() dto: LoginDto, @Ip() ip: string, @Headers('user-agent') ua: string): Promise { - await this.captchaService.checkImgCaptcha(dto.captchaId, dto.verifyCode) - const token = await this.authService.login( - dto.username, - dto.password, - ip, - ua, - ) - return { token } + @Body() dto: LoginDto, + @Ip() ip: string, + @Headers('user-agent') ua: string + ): Promise { + await this.captchaService.checkImgCaptcha(dto.captchaId, dto.verifyCode); + const token = await this.authService.login(dto.username, dto.password, ip, ua); + 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/src/modules/auth/auth.module.ts b/src/modules/auth/auth.module.ts index edaf919..8d67c3a 100644 --- a/src/modules/auth/auth.module.ts +++ b/src/modules/auth/auth.module.ts @@ -1,38 +1,33 @@ -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 { ConfigKeyPaths, ISecurityConfig } from '~/config' -import { isDev } from '~/global/env' +import { ConfigKeyPaths, 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 controllers = [AuthController, AccountController, CaptchaController, EmailController]; +const providers = [AuthService, TokenService, CaptchaService]; +const strategies = [LocalStrategy, JwtStrategy]; @Module({ imports: [ @@ -41,14 +36,13 @@ const strategies = [LocalStrategy, JwtStrategy] JwtModule.registerAsync({ imports: [ConfigModule], useFactory: (configService: ConfigService) => { - const { jwtSecret, jwtExprire } - = configService.get('security') + const { jwtSecret, jwtExprire } = configService.get('security'); return { secret: jwtSecret, expires: jwtExprire, ignoreExpiration: isDev, - } + }; }, inject: [ConfigService], }), diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index b734e27..8f97c97 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -1,23 +1,23 @@ -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 { BusinessException } from '~/common/exceptions/biz.exception'; -import { ErrorEnum } from '~/constants/error-code.constant' -import { genAuthPVKey, genAuthPermKey, genAuthTokenKey } from '~/helper/genRedisKey' +import { ErrorEnum } from '~/constants/error-code.constant'; +import { genAuthPVKey, genAuthPermKey, genAuthTokenKey } from '~/helper/genRedisKey'; -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 { @@ -27,130 +27,123 @@ export class AuthService { private roleService: RoleService, private userService: UserService, private loginLogService: LoginLogService, - private tokenService: TokenService, + private tokenService: TokenService ) {} 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) + if (isEmpty(user)) 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; } /** * 获取登录JWT * 返回null则账号密码有误,不存在该用户 */ - async login( - username: string, - password: string, - ip: string, - ua: string, - ): Promise { - const user = await this.userService.findUserByUserName(username) - if (isEmpty(user)) - throw new BusinessException(ErrorEnum.INVALID_USERNAME_PASSWORD) + async login(username: string, password: string, ip: string, ua: string): Promise { + const user = await this.userService.findUserByUserName(username); + if (isEmpty(user)) 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(genAuthTokenKey(user.id), token.accessToken) + await this.redis.set(genAuthTokenKey(user.id), token.accessToken); // 设置密码版本号 当密码修改时,版本号+1 - await this.redis.set(genAuthPVKey(user.id), 1) + await this.redis.set(genAuthPVKey(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(genAuthPermKey(uid)) - return permissionString ? JSON.parse(permissionString) : [] + const permissionString = await this.redis.get(genAuthPermKey(uid)); + return permissionString ? JSON.parse(permissionString) : []; } async setPermissionsCache(uid: number, permissions: string[]): Promise { - await this.redis.set(genAuthPermKey(uid), JSON.stringify(permissions)) + await this.redis.set(genAuthPermKey(uid), JSON.stringify(permissions)); } async getPasswordVersionByUid(uid: number): Promise { - return this.redis.get(genAuthPVKey(uid)) + return this.redis.get(genAuthPVKey(uid)); } async getTokenByUid(uid: number): Promise { - return this.redis.get(genAuthTokenKey(uid)) + return this.redis.get(genAuthTokenKey(uid)); } } diff --git a/src/modules/auth/controllers/account.controller.ts b/src/modules/auth/controllers/account.controller.ts index d6072a1..ec3a4f9 100644 --- a/src/modules/auth/controllers/account.controller.ts +++ b/src/modules/auth/controllers/account.controller.ts @@ -1,19 +1,19 @@ -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 { 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 { AccountMenus, 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 { AccountMenus, AccountUpdateDto } from '../dto/account.dto'; +import { JwtAuthGuard } from '../guards/jwt-auth.guard'; @ApiTags('Account - 账户模块') @ApiSecurityAuth() @@ -23,7 +23,7 @@ import { JwtAuthGuard } from '../guards/jwt-auth.guard' export class AccountController { constructor( private userService: UserService, - private authService: AuthService, + private authService: AuthService ) {} @Get('profile') @@ -31,14 +31,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') @@ -46,7 +46,7 @@ export class AccountController { @ApiResult({ type: [AccountMenus] }) @AllowAnon() async menu(@AuthUser() user: IAuthUser): Promise { - return this.authService.getMenus(user.uid) + return this.authService.getMenus(user.uid); } @Get('permissions') @@ -54,22 +54,20 @@ 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') @ApiOperation({ summary: '更改账户资料' }) @AllowAnon() - async update( - @AuthUser() user: IAuthUser, @Body() dto: AccountUpdateDto): Promise { - await this.userService.updateAccountInfo(user.uid, dto) + async update(@AuthUser() user: IAuthUser, @Body() dto: AccountUpdateDto): Promise { + await this.userService.updateAccountInfo(user.uid, dto); } @Post('password') @ApiOperation({ summary: '更改账户密码' }) @AllowAnon() - async password( - @AuthUser() user: IAuthUser, @Body() dto: PasswordUpdateDto): Promise { - await this.userService.updatePassword(user.uid, dto) + async password(@AuthUser() user: IAuthUser, @Body() dto: PasswordUpdateDto): Promise { + await this.userService.updatePassword(user.uid, dto); } } diff --git a/src/modules/auth/controllers/captcha.controller.ts b/src/modules/auth/controllers/captcha.controller.ts index 154236c..4200db0 100644 --- a/src/modules/auth/controllers/captcha.controller.ts +++ b/src/modules/auth/controllers/captcha.controller.ts @@ -1,19 +1,19 @@ -import { InjectRedis } from '@liaoliaots/nestjs-redis' -import { Controller, Get, Query } from '@nestjs/common' -import { ApiOperation, ApiTags } from '@nestjs/swagger' +import { InjectRedis } from '@liaoliaots/nestjs-redis'; +import { Controller, Get, Query } from '@nestjs/common'; +import { ApiOperation, ApiTags } from '@nestjs/swagger'; -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 { genCaptchaImgKey } from '~/helper/genRedisKey' -import { generateUUID } from '~/utils' +import { ApiResult } from '~/common/decorators/api-result.decorator'; +import { genCaptchaImgKey } from '~/helper/genRedisKey'; +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) @@ -27,7 +27,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, @@ -36,15 +36,13 @@ 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', - )}`, + img: `data:image/svg+xml;base64,${Buffer.from(svg.data).toString('base64')}`, id: generateUUID(), - } + }; // 5分钟过期时间 - await this.redis.set(genCaptchaImgKey(result.id), svg.text, 'EX', 60 * 5) - return result + await this.redis.set(genCaptchaImgKey(result.id), svg.text, 'EX', 60 * 5); + return result; } } diff --git a/src/modules/auth/controllers/email.controller.ts b/src/modules/auth/controllers/email.controller.ts index 99ce81a..f0fb6c4 100644 --- a/src/modules/auth/controllers/email.controller.ts +++ b/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 { Ip } from '~/common/decorators/http.decorator'; -import { MailerService } from '~/shared/mailer/mailer.service' +import { MailerService } from '~/shared/mailer/mailer.service'; -import { Public } from '../decorators/public.decorator' +import { Public } from '../decorators/public.decorator'; -import { SendEmailCodeDto } from '../dto/captcha.dto' +import { SendEmailCodeDto } from '../dto/captcha.dto'; @ApiTags('Auth - 认证模块') @UseGuards(ThrottlerGuard) @@ -21,17 +21,14 @@ export class EmailController { @ApiOperation({ summary: '发送邮箱验证码' }) @Public() @Throttle({ default: { limit: 2, ttl: 600000 } }) - async sendEmailCode( - @Body() dto: SendEmailCodeDto, - @Ip() ip: string, - ): Promise { + async sendEmailCode(@Body() dto: SendEmailCodeDto, @Ip() ip: string): 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.sendVerificationCode(email) + await this.mailerService.checkLimit(email, ip); + const { code } = await this.mailerService.sendVerificationCode(email); - await this.mailerService.log(email, code, ip) + await this.mailerService.log(email, code, ip); } // @Post() diff --git a/src/modules/auth/decorators/allow-anon.decorator.ts b/src/modules/auth/decorators/allow-anon.decorator.ts index 21954f6..b70dfac 100644 --- a/src/modules/auth/decorators/allow-anon.decorator.ts +++ b/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 '../auth.constant' +import { ALLOW_ANON_KEY } from '../auth.constant'; /** * 当接口不需要检测用户是否具有操作权限时添加该装饰器 */ -export const AllowAnon = () => SetMetadata(ALLOW_ANON_KEY, true) +export const AllowAnon = () => SetMetadata(ALLOW_ANON_KEY, true); diff --git a/src/modules/auth/decorators/auth-user.decorator.ts b/src/modules/auth/decorators/auth-user.decorator.ts index ef0733d..bac65da 100644 --- a/src/modules/auth/decorators/auth-user.decorator.ts +++ b/src/modules/auth/decorators/auth-user.decorator.ts @@ -1,17 +1,15 @@ -import { ExecutionContext, createParamDecorator } from '@nestjs/common' -import { FastifyRequest } from 'fastify' +import { ExecutionContext, createParamDecorator } 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() - // auth guard will mount this - const user = request.user as IAuthUser +export const AuthUser = createParamDecorator((data: Payload, ctx: ExecutionContext) => { + const request = ctx.switchToHttp().getRequest(); + // auth guard will mount this + const user = request.user as IAuthUser; - return data ? user?.[data] : user - }, -) + return data ? user?.[data] : user; +}); diff --git a/src/modules/auth/decorators/permission.decorator.ts b/src/modules/auth/decorators/permission.decorator.ts index 943679f..e88096c 100644 --- a/src/modules/auth/decorators/permission.decorator.ts +++ b/src/modules/auth/decorators/permission.decorator.ts @@ -1,23 +1,23 @@ -import { SetMetadata, applyDecorators } from '@nestjs/common' +import { SetMetadata, applyDecorators } from '@nestjs/common'; -import { isPlainObject } from 'lodash' +import { isPlainObject } from 'lodash'; -import { PERMISSION_KEY } from '../auth.constant' +import { PERMISSION_KEY } from '../auth.constant'; - type TupleToObject> = { - [K in Uppercase]: `${T}:${Lowercase}` - } - type AddPrefixToObjectValue> = { - [K in keyof P]: K extends string ? `${T}:${P[K]}` : never - } +type TupleToObject> = { + [K in Uppercase]: `${T}:${Lowercase}`; +}; +type AddPrefixToObjectValue> = { + [K in keyof P]: K extends string ? `${T}:${P[K]}` : never; +}; /** 资源操作需要特定的权限 */ export function Perm(permission: string | string[]) { - return applyDecorators(SetMetadata(PERMISSION_KEY, permission)) + return applyDecorators(SetMetadata(PERMISSION_KEY, permission)); } /** (此举非必需)保存通过 definePermission 定义的所有权限,可用于前端开发人员开发阶段的 ts 类型提示,避免前端权限定义与后端定义不匹配 */ -let permissions: string[] = [] +let permissions: string[] = []; /** * 定义权限,同时收集所有被定义的权限 * @@ -33,26 +33,31 @@ let permissions: string[] = [] * definePermission('app:health', ['network']); * ``` */ -export function definePermission>(modulePrefix: T, actionMap: U): AddPrefixToObjectValue -export function definePermission>(modulePrefix: T, actions: U): TupleToObject +export function definePermission>( + modulePrefix: T, + actionMap: U +): AddPrefixToObjectValue; +export function definePermission>( + modulePrefix: T, + actions: U +): TupleToObject; export function definePermission(modulePrefix: string, actions) { if (isPlainObject(actions)) { Object.entries(actions).forEach(([key, action]) => { - actions[key] = `${modulePrefix}:${action}` - }) - permissions = [...new Set([...permissions, ...Object.values(actions)])] - return actions - } - else if (Array.isArray(actions)) { - const permissionFormats = actions.map(action => `${modulePrefix}:${action}`) - permissions = [...new Set([...permissions, ...permissionFormats])] + actions[key] = `${modulePrefix}:${action}`; + }); + permissions = [...new Set([...permissions, ...Object.values(actions)])]; + return actions; + } else if (Array.isArray(actions)) { + const permissionFormats = actions.map(action => `${modulePrefix}:${action}`); + permissions = [...new Set([...permissions, ...permissionFormats])]; return actions.reduce((prev, action) => { - prev[action.toUpperCase()] = `${modulePrefix}:${action}` - return prev - }, {}) + prev[action.toUpperCase()] = `${modulePrefix}:${action}`; + return prev; + }, {}); } } /** 获取所有通过 definePermission 定义的权限 */ -export const getDefinePermissions = () => permissions +export const getDefinePermissions = () => permissions; diff --git a/src/modules/auth/decorators/public.decorator.ts b/src/modules/auth/decorators/public.decorator.ts index c3409ca..b9592f2 100644 --- a/src/modules/auth/decorators/public.decorator.ts +++ b/src/modules/auth/decorators/public.decorator.ts @@ -1,8 +1,8 @@ -import { SetMetadata } from '@nestjs/common' +import { SetMetadata } from '@nestjs/common'; -import { PUBLIC_KEY } from '../auth.constant' +import { PUBLIC_KEY } from '../auth.constant'; /** * 当接口不需要检测用户登录时添加该装饰器 */ -export const Public = () => SetMetadata(PUBLIC_KEY, true) +export const Public = () => SetMetadata(PUBLIC_KEY, true); diff --git a/src/modules/auth/decorators/resource.decorator.ts b/src/modules/auth/decorators/resource.decorator.ts index 73143b0..6734598 100644 --- a/src/modules/auth/decorators/resource.decorator.ts +++ b/src/modules/auth/decorators/resource.decorator.ts @@ -1,12 +1,22 @@ -import { SetMetadata, applyDecorators } from '@nestjs/common' +import { SetMetadata, applyDecorators } from '@nestjs/common'; -import { ObjectLiteral, ObjectType, Repository } from 'typeorm' +import { ObjectLiteral, ObjectType, Repository } from 'typeorm'; -import { RESOURCE_KEY } from '../auth.constant' +import { RESOURCE_KEY } from '../auth.constant'; -export type Condition = (Repository: Repository, items: number[], user: IAuthUser) => Promise +export type Condition = ( + Repository: Repository, + items: number[], + user: IAuthUser +) => Promise; -export interface ResourceObject { entity: ObjectType, condition: Condition } -export function Resource(entity: ObjectType, condition?: Condition) { - return applyDecorators(SetMetadata(RESOURCE_KEY, { entity, condition })) +export interface ResourceObject { + entity: ObjectType; + condition: Condition; +} +export function Resource( + entity: ObjectType, + condition?: Condition +) { + return applyDecorators(SetMetadata(RESOURCE_KEY, { entity, condition })); } diff --git a/src/modules/auth/dto/account.dto.ts b/src/modules/auth/dto/account.dto.ts index 8b3a5ee..e5b73e5 100644 --- a/src/modules/auth/dto/account.dto.ts +++ b/src/modules/auth/dto/account.dto.ts @@ -1,24 +1,17 @@ -import { ApiProperty, OmitType, PartialType, PickType } from '@nestjs/swagger' -import { - IsEmail, - IsOptional, - IsString, - Matches, - MaxLength, - MinLength, -} from 'class-validator' +import { ApiProperty, OmitType, PartialType, PickType } from '@nestjs/swagger'; +import { IsEmail, IsOptional, IsString, Matches, MaxLength, MinLength } from 'class-validator'; -import { MenuEntity } from '~/modules/system/menu/menu.entity' +import { MenuEntity } from '~/modules/system/menu/menu.entity'; export class AccountUpdateDto { @ApiProperty({ description: '用户呢称' }) @IsString() @IsOptional() - nickname: string + nickname: string; @ApiProperty({ description: '用户邮箱' }) @IsEmail() - email: string + email: string; @ApiProperty({ description: '用户QQ' }) @IsOptional() @@ -26,39 +19,54 @@ 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; } -export class MenuMeta extends PartialType(OmitType(MenuEntity, ['parentId', 'createdAt', 'updatedAt', 'id', 'roles', 'path', 'name'] as const)) { - title: string +export class MenuMeta extends PartialType( + OmitType(MenuEntity, [ + 'parentId', + 'createdAt', + 'updatedAt', + 'id', + 'roles', + 'path', + 'name', + ] as const) +) { + title: string; } -export class AccountMenus extends PickType(MenuEntity, ['id', 'path', 'name', 'component'] as const) { - meta: MenuMeta +export class AccountMenus extends PickType(MenuEntity, [ + 'id', + 'path', + 'name', + 'component', +] as const) { + meta: MenuMeta; } diff --git a/src/modules/auth/dto/auth.dto.ts b/src/modules/auth/dto/auth.dto.ts index abaf379..8cce9d0 100644 --- a/src/modules/auth/dto/auth.dto.ts +++ b/src/modules/auth/dto/auth.dto.ts @@ -1,43 +1,43 @@ -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; @ApiProperty({ description: '验证码标识' }) @IsString() - captchaId: string + captchaId: string; @ApiProperty({ description: '用户输入的验证码' }) @IsString() @MinLength(4) @MaxLength(4) - verifyCode: string + verifyCode: 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/src/modules/auth/dto/captcha.dto.ts b/src/modules/auth/dto/captcha.dto.ts index c24d3d8..8f8c0bf 100644 --- a/src/modules/auth/dto/captcha.dto.ts +++ b/src/modules/auth/dto/captcha.dto.ts @@ -1,12 +1,6 @@ -import { ApiProperty } from '@nestjs/swagger' -import { Type } from 'class-transformer' -import { - IsEmail, - IsInt, - IsMobilePhone, - IsOptional, - IsString, -} from 'class-validator' +import { ApiProperty } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { IsEmail, IsInt, IsMobilePhone, IsOptional, IsString } from 'class-validator'; export class ImageCaptchaDto { @ApiProperty({ @@ -17,7 +11,7 @@ export class ImageCaptchaDto { @Type(() => Number) @IsInt() @IsOptional() - readonly width: number = 100 + readonly width: number = 100; @ApiProperty({ required: false, @@ -27,27 +21,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/src/modules/auth/entities/access-token.entity.ts b/src/modules/auth/entities/access-token.entity.ts index d56afc8..e57a142 100644 --- a/src/modules/auth/entities/access-token.entity.ts +++ b/src/modules/auth/entities/access-token.entity.ts @@ -7,34 +7,34 @@ import { ManyToOne, OneToOne, PrimaryGeneratedColumn, -} from 'typeorm' +} from 'typeorm'; -import { UserEntity } from '~/modules/user/user.entity' +import { UserEntity } from '~/modules/user/user.entity'; -import { RefreshTokenEntity } from './refresh-token.entity' +import { RefreshTokenEntity } from './refresh-token.entity'; @Entity('user_access_tokens') export class AccessTokenEntity extends BaseEntity { @PrimaryGeneratedColumn('uuid') - id!: string + id!: string; @Column({ length: 500 }) - value!: string + value!: string; @Column({ comment: '令牌过期时间' }) - expired_at!: Date + expired_at!: Date; @CreateDateColumn({ comment: '令牌创建时间' }) - created_at!: Date + created_at!: Date; @OneToOne(() => RefreshTokenEntity, refreshToken => refreshToken.accessToken, { cascade: true, }) - refreshToken!: RefreshTokenEntity + refreshToken!: RefreshTokenEntity; @ManyToOne(() => UserEntity, user => user.accessTokens, { onDelete: 'CASCADE', }) @JoinColumn({ name: 'user_id' }) - user!: UserEntity + user!: UserEntity; } diff --git a/src/modules/auth/entities/refresh-token.entity.ts b/src/modules/auth/entities/refresh-token.entity.ts index cec190b..b511922 100644 --- a/src/modules/auth/entities/refresh-token.entity.ts +++ b/src/modules/auth/entities/refresh-token.entity.ts @@ -6,27 +6,27 @@ import { JoinColumn, OneToOne, PrimaryGeneratedColumn, -} from 'typeorm' +} from 'typeorm'; -import { AccessTokenEntity } from './access-token.entity' +import { AccessTokenEntity } from './access-token.entity'; @Entity('user_refresh_tokens') export class RefreshTokenEntity extends BaseEntity { @PrimaryGeneratedColumn('uuid') - id!: string + id!: string; @Column({ length: 500 }) - value!: string + value!: string; @Column({ comment: '令牌过期时间' }) - expired_at!: Date + expired_at!: Date; @CreateDateColumn({ comment: '令牌创建时间' }) - created_at!: Date + created_at!: Date; @OneToOne(() => AccessTokenEntity, accessToken => accessToken.refreshToken, { onDelete: 'CASCADE', }) @JoinColumn() - accessToken!: AccessTokenEntity + accessToken!: AccessTokenEntity; } diff --git a/src/modules/auth/guards/jwt-auth.guard.ts b/src/modules/auth/guards/jwt-auth.guard.ts index 0cbe88c..052c87b 100644 --- a/src/modules/auth/guards/jwt-auth.guard.ts +++ b/src/modules/auth/guards/jwt-auth.guard.ts @@ -1,21 +1,17 @@ -import { - ExecutionContext, - Injectable, - UnauthorizedException, -} from '@nestjs/common' -import { Reflector } from '@nestjs/core' -import { AuthGuard } from '@nestjs/passport' -import { FastifyRequest } from 'fastify' -import { isEmpty, isNil } from 'lodash' +import { ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { AuthGuard } from '@nestjs/passport'; +import { 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 { BusinessException } from '~/common/exceptions/biz.exception'; +import { ErrorEnum } from '~/constants/error-code.constant'; +import { AuthService } from '~/modules/auth/auth.service'; -import { checkIsDemoMode } from '~/utils' +import { checkIsDemoMode } from '~/utils'; -import { AuthStrategy, PUBLIC_KEY } from '../auth.constant' -import { TokenService } from '../services/token.service' +import { AuthStrategy, PUBLIC_KEY } from '../auth.constant'; +import { TokenService } from '../services/token.service'; // https://docs.nestjs.com/recipes/passport#implement-protected-route-and-jwt-strategy-guards @Injectable() @@ -23,66 +19,60 @@ export class JwtAuthGuard extends AuthGuard(AuthStrategy.JWT) { constructor( private reflector: Reflector, private authService: AuthService, - private tokenService: TokenService, + private tokenService: TokenService ) { - super() + super(); } async canActivate(context: ExecutionContext): Promise { const isPublic = this.reflector.getAllAndOverride(PUBLIC_KEY, [ context.getHandler(), context.getClass(), - ]) - const request = context.switchToHttp().getRequest() + ]); + const request = context.switchToHttp().getRequest(); // const response = context.switchToHttp().getResponse() // TODO 此处代码的作用是判断如果在演示环境下,则拒绝用户的增删改操作,去掉此代码不影响正常的业务逻辑 - if (request.method !== 'GET' && !request.url.includes('/auth/login')) - checkIsDemoMode() + if (request.method !== 'GET' && !request.url.includes('/auth/login')) checkIsDemoMode(); - const isSse = request.headers.accept === 'text/event-stream' + const isSse = request.headers.accept === 'text/event-stream'; if (isSse && !request.headers.authorization?.startsWith('Bearer')) { - const { token } = request.query as Record - if (token) - request.headers.authorization = `Bearer ${token}` + const { token } = request.query as Record; + if (token) request.headers.authorization = `Bearer ${token}`; } - const Authorization = request.headers.authorization + const Authorization = request.headers.authorization; - let result: any = false + let result: any = false; try { - result = await super.canActivate(context) - } - catch (e) { + result = await super.canActivate(context); + } catch (e) { // 需要后置判断 这样携带了 token 的用户就能够解析到 request.user - if (isPublic) - return true + if (isPublic) return true; - if (isEmpty(Authorization)) - throw new UnauthorizedException('未登录') + if (isEmpty(Authorization)) 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('令牌无效'); } // SSE 请求 if (isSse) { - const { uid } = request.params as Record + const { uid } = request.params as Record; if (Number(uid) !== request.user.uid) - throw new UnauthorizedException('路径参数 uid 与当前 token 登录的用户 uid 不一致') + throw new UnauthorizedException('路径参数 uid 与当前 token 登录的用户 uid 不一致'); } - 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); } // 不允许多端登录 @@ -92,14 +82,13 @@ 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() + if (err || !user) throw err || new UnauthorizedException(); - return user + return user; } } diff --git a/src/modules/auth/guards/local.guard.ts b/src/modules/auth/guards/local.guard.ts index c2de171..2bcaca9 100644 --- a/src/modules/auth/guards/local.guard.ts +++ b/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 '../auth.constant' +import { AuthStrategy } from '../auth.constant'; @Injectable() export class LocalGuard extends AuthGuard(AuthStrategy.LOCAL) { async canActivate(context: ExecutionContext) { - return true + return true; } } diff --git a/src/modules/auth/guards/rbac.guard.ts b/src/modules/auth/guards/rbac.guard.ts index 8545adb..408a747 100644 --- a/src/modules/auth/guards/rbac.guard.ts +++ b/src/modules/auth/guards/rbac.guard.ts @@ -1,76 +1,64 @@ -import { - CanActivate, - ExecutionContext, - Injectable, - UnauthorizedException, -} from '@nestjs/common' -import { Reflector } from '@nestjs/core' -import { FastifyRequest } from 'fastify' +import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } 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, PUBLIC_KEY, Roles } from '../auth.constant' +import { ALLOW_ANON_KEY, PERMISSION_KEY, PUBLIC_KEY, Roles } from '../auth.constant'; @Injectable() export class RbacGuard implements CanActivate { constructor( private reflector: Reflector, - private authService: AuthService, + private authService: AuthService ) {} async canActivate(context: ExecutionContext): Promise { const isPublic = this.reflector.getAllAndOverride(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 + const allowAnon = this.reflector.get(ALLOW_ANON_KEY, context.getHandler()); + if (allowAnon) return true; - const payloadPermission = this.reflector.getAllAndOverride< - string | string[] - >(PERMISSION_KEY, [context.getHandler(), context.getClass()]) + const payloadPermission = this.reflector.getAllAndOverride(PERMISSION_KEY, [ + context.getHandler(), + context.getClass(), + ]); // 控制器没有设置接口权限,则默认通过 - if (!payloadPermission) - return true + if (!payloadPermission) return true; // 管理员放开所有权限 - if (user.roles.includes(Roles.ADMIN)) - return true + if (user.roles.includes(Roles.ADMIN)) return true; - const allPermissions = await this.authService.getPermissionsCache(user.uid) ?? await this.authService.getPermissions(user.uid) + const allPermissions = + (await this.authService.getPermissionsCache(user.uid)) ?? + (await this.authService.getPermissions(user.uid)); // console.log(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) + if (typeof payloadPermission === 'string') canNext = allPermissions.includes(payloadPermission); - if (!canNext) - throw new BusinessException(ErrorEnum.NO_PERMISSION) + if (!canNext) throw new BusinessException(ErrorEnum.NO_PERMISSION); - return true + return true; } } diff --git a/src/modules/auth/guards/resource.guard.ts b/src/modules/auth/guards/resource.guard.ts index b78da0c..db45299 100644 --- a/src/modules/auth/guards/resource.guard.ts +++ b/src/modules/auth/guards/resource.guard.ts @@ -1,72 +1,67 @@ -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 { isArray, isEmpty, isNil } from 'lodash' +import { isArray, isEmpty, isNil } from 'lodash'; -import { DataSource, In, Repository } from 'typeorm' +import { DataSource, In, Repository } from 'typeorm'; -import { BusinessException } from '~/common/exceptions/biz.exception' +import { BusinessException } from '~/common/exceptions/biz.exception'; -import { ErrorEnum } from '~/constants/error-code.constant' +import { ErrorEnum } from '~/constants/error-code.constant'; -import { PUBLIC_KEY, RESOURCE_KEY, Roles } from '../auth.constant' -import { ResourceObject } from '../decorators/resource.decorator' +import { PUBLIC_KEY, RESOURCE_KEY, Roles } from '../auth.constant'; +import { ResourceObject } from '../decorators/resource.decorator'; @Injectable() export class ResourceGuard implements CanActivate { constructor( private reflector: Reflector, - private dataSource: DataSource, + private dataSource: DataSource ) {} async canActivate(context: ExecutionContext): Promise { const isPublic = this.reflector.getAllAndOverride(PUBLIC_KEY, [ context.getHandler(), context.getClass(), - ]) + ]); - const request = context.switchToHttp().getRequest() - const isSse = request.headers.accept === 'text/event-stream' + const request = context.switchToHttp().getRequest(); + const isSse = request.headers.accept === 'text/event-stream'; // 忽略 sse 请求 - if (isPublic || isSse) - return true + if (isPublic || isSse) return true; - const { user } = request + const { user } = request; - if (!user) - return false + if (!user) return false; // 如果是检查资源所属,且不是超级管理员,还需要进一步判断是否是自己的数据 const { entity, condition } = this.reflector.get( RESOURCE_KEY, - context.getHandler(), - ) ?? { entity: null, condition: null } + context.getHandler() + ) ?? { 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 (ids) 验证数据拥有者 * @param request */ const getRequestItems = (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 (id) - return [id] + if (id) return [id]; - const { items } = body - return !isNil(items) && isArray(items) ? items : [] - } + const { items } = body; + return !isNil(items) && isArray(items) ? items : []; + }; - const items = getRequestItems(request) - if (isEmpty(items)) - throw new BusinessException(ErrorEnum.REQUESTED_RESOURCE_NOT_FOUND) + const items = getRequestItems(request); + if (isEmpty(items)) throw new BusinessException(ErrorEnum.REQUESTED_RESOURCE_NOT_FOUND); - if (condition) - return condition(repo, items, user) + if (condition) return condition(repo, items, user); const recordQuery = { where: { @@ -74,14 +69,13 @@ export class ResourceGuard implements CanActivate { user: { id: user.uid }, }, relations: ['user'], - } + }; - const records = await repo.find(recordQuery) + const records = await repo.find(recordQuery); - if (isEmpty(records)) - throw new BusinessException(ErrorEnum.REQUESTED_RESOURCE_NOT_FOUND) + if (isEmpty(records)) throw new BusinessException(ErrorEnum.REQUESTED_RESOURCE_NOT_FOUND); } - return true + return true; } } diff --git a/src/modules/auth/models/auth.model.ts b/src/modules/auth/models/auth.model.ts index faeada1..01a5e08 100644 --- a/src/modules/auth/models/auth.model.ts +++ b/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/src/modules/auth/services/captcha.service.ts b/src/modules/auth/services/captcha.service.ts index 612a6b7..f7ddfc0 100644 --- a/src/modules/auth/services/captcha.service.ts +++ b/src/modules/auth/services/captcha.service.ts @@ -1,40 +1,35 @@ -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 { genCaptchaImgKey } from '~/helper/genRedisKey' -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 { genCaptchaImgKey } from '~/helper/genRedisKey'; +import { CaptchaLogService } from '~/modules/system/log/services/captcha-log.service'; @Injectable() export class CaptchaService { constructor( @InjectRedis() private redis: Redis, - private captchaLogService: CaptchaLogService, + private captchaLogService: CaptchaLogService ) {} /** * 校验图片验证码 */ async checkImgCaptcha(id: string, code: string): Promise { - const result = await this.redis.get(genCaptchaImgKey(id)) + const result = await this.redis.get(genCaptchaImgKey(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(genCaptchaImgKey(id)) + await this.redis.del(genCaptchaImgKey(id)); } - async log( - account: string, - code: string, - provider: 'sms' | 'email', - uid?: number, - ): Promise { - await this.captchaLogService.create(account, code, provider, uid) + async log(account: string, code: string, provider: 'sms' | 'email', uid?: number): Promise { + await this.captchaLogService.create(account, code, provider, uid); } } diff --git a/src/modules/auth/services/token.service.ts b/src/modules/auth/services/token.service.ts index e680ecc..786090f 100644 --- a/src/modules/auth/services/token.service.ts +++ b/src/modules/auth/services/token.service.ts @@ -1,14 +1,14 @@ -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/user.entity' -import { generateUUID } from '~/utils' +import { ISecurityConfig, SecurityConfig } from '~/config'; +import { RoleService } from '~/modules/system/role/role.service'; +import { UserEntity } from '~/modules/user/user.entity'; +import { generateUUID } from '~/utils'; -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'; /** * 令牌服务 @@ -18,7 +18,7 @@ export class TokenService { constructor( private jwtService: JwtService, private roleService: RoleService, - @Inject(SecurityConfig.KEY) private securityConfig: ISecurityConfig, + @Inject(SecurityConfig.KEY) private securityConfig: ISecurityConfig ) {} /** @@ -27,30 +27,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 +57,25 @@ 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 - accessToken.expired_at = dayjs() - .add(this.securityConfig.jwtExprire, 'second') - .toDate() + const accessToken = new AccessTokenEntity(); + accessToken.value = jwtSign; + accessToken.user = { id: uid } as UserEntity; + accessToken.expired_at = dayjs().add(this.securityConfig.jwtExprire, 'second').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, - } + }; } /** @@ -86,28 +83,23 @@ export class TokenService { * @param accessToken * @param now */ - async generateRefreshToken( - accessToken: AccessTokenEntity, - now: dayjs.Dayjs, - ): Promise { + async generateRefreshToken(accessToken: AccessTokenEntity, now: dayjs.Dayjs): Promise { const refreshTokenPayload = { uuid: generateUUID(), - } + }; const refreshTokenSign = this.jwtService.sign(refreshTokenPayload, { secret: this.securityConfig.refreshSecret, - }) + }); - const refreshToken = new RefreshTokenEntity() - refreshToken.value = refreshTokenSign - refreshToken.expired_at = now - .add(this.securityConfig.refreshExpire, 'second') - .toDate() - refreshToken.accessToken = accessToken + const refreshToken = new RefreshTokenEntity(); + refreshToken.value = refreshTokenSign; + refreshToken.expired_at = now.add(this.securityConfig.refreshExpire, 'second').toDate(); + refreshToken.accessToken = accessToken; - await refreshToken.save() + await refreshToken.save(); - return refreshTokenSign + return refreshTokenSign; } /** @@ -119,7 +111,7 @@ export class TokenService { where: { value }, relations: ['user', 'refreshToken'], cache: true, - }) + }); } /** @@ -129,9 +121,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(); } /** @@ -142,11 +133,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(); } } @@ -155,6 +145,6 @@ export class TokenService { * @param token */ async verifyAccessToken(token: string): Promise { - return this.jwtService.verify(token) + return this.jwtService.verify(token); } } diff --git a/src/modules/auth/strategies/jwt.strategy.ts b/src/modules/auth/strategies/jwt.strategy.ts index 1fc00fd..caf763b 100644 --- a/src/modules/auth/strategies/jwt.strategy.ts +++ b/src/modules/auth/strategies/jwt.strategy.ts @@ -1,24 +1,22 @@ -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 '../auth.constant' +import { AuthStrategy } from '../auth.constant'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy, AuthStrategy.JWT) { - constructor( - @Inject(SecurityConfig.KEY) private securityConfig: ISecurityConfig, - ) { + constructor(@Inject(SecurityConfig.KEY) private securityConfig: ISecurityConfig) { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: securityConfig.jwtSecret, - }) + }); } async validate(payload: IAuthUser) { - return payload + return payload; } } diff --git a/src/modules/auth/strategies/local.strategy.ts b/src/modules/auth/strategies/local.strategy.ts index 69c59a9..f0c10da 100644 --- a/src/modules/auth/strategies/local.strategy.ts +++ b/src/modules/auth/strategies/local.strategy.ts @@ -1,24 +1,21 @@ -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 { AuthStrategy } from '../auth.constant' -import { AuthService } from '../auth.service' +import { AuthStrategy } from '../auth.constant'; +import { AuthService } from '../auth.service'; @Injectable() -export class LocalStrategy extends PassportStrategy( - Strategy, - AuthStrategy.LOCAL, -) { +export class LocalStrategy extends PassportStrategy(Strategy, AuthStrategy.LOCAL) { constructor(private authService: AuthService) { 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/src/modules/contract/contract.controller.ts b/src/modules/contract/contract.controller.ts new file mode 100644 index 0000000..7a20986 --- /dev/null +++ b/src/modules/contract/contract.controller.ts @@ -0,0 +1,63 @@ +import { Controller, Get } from '@nestjs/common'; +import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { definePermission } from '../auth/decorators/permission.decorator'; +import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator'; +import { ContractService } from './contract.service'; +import { ApiResult } from '~/common/decorators/api-result.decorator'; +export const permissions = definePermission('app:contract', { + LIST: 'list', + CREATE: 'create', + READ: 'read', + UPDATE: 'update', + DELETE: 'delete', +} as const); + +@ApiTags('Contract - 合同') +@ApiSecurityAuth() +@Controller('contract') +export class ContractController { + constructor(private menuService: ContractService) {} + + // @Get() + // @ApiOperation({ summary: '获取合同列表' }) + // @ApiResult({ type: [RoleEntity], isPage: true }) + // @Perm(permissions.LIST) + // async list(@Query() dto: RoleQueryDto) { + // return this.roleService.findAll(dto) + // } + + // @Get(':id') + // @ApiOperation({ summary: '获取角色信息' }) + // @ApiResult({ type: RoleInfo }) + // @Perm(permissions.READ) + // async info(@IdParam() id: number) { + // return this.roleService.info(id) + // } + + // @Post() + // @ApiOperation({ summary: '新增角色' }) + // @Perm(permissions.CREATE) + // async create(@Body() dto: RoleDto): Promise { + // await this.roleService.create(dto) + // } + + // @Put(':id') + // @ApiOperation({ summary: '更新角色' }) + // @Perm(permissions.UPDATE) + // async update( + // @IdParam() id: number, @Body() dto: RoleUpdateDto): Promise { + // await this.roleService.update(id, dto) + // await this.menuService.refreshOnlineUserPerms() + // } + + // @Delete(':id') + // @ApiOperation({ summary: '删除角色' }) + // @Perm(permissions.DELETE) + // async delete(@IdParam() id: number): Promise { + // if (await this.roleService.checkUserByRoleId(id)) + // throw new BadRequestException('该角色存在关联用户,无法删除') + + // await this.roleService.delete(id) + // await this.menuService.refreshOnlineUserPerms() + // } +} diff --git a/src/modules/contract/contract.entity.ts b/src/modules/contract/contract.entity.ts new file mode 100644 index 0000000..108c153 --- /dev/null +++ b/src/modules/contract/contract.entity.ts @@ -0,0 +1,39 @@ +import { ApiHideProperty, ApiProperty } from '@nestjs/swagger'; +import { Column, Entity, JoinTable, ManyToMany, Relation } from 'typeorm'; +import { CommonEntity } from '~/common/entity/common.entity'; + +@Entity({ name: 'contract' }) +export class RoleEntity extends CommonEntity { + @Column({ length: 100, unique: true, name: 'contract_number' }) + @ApiProperty({ description: '合同编号' }) + contractNumber: string; + + @Column({ length: 255, name: 'title', type: 'varchar' }) + @ApiProperty({ description: '合同标题' }) + title: string; + + @Column() + @ApiProperty({ description: '合同类型', type: 'varchar' }) + type: number; + + @Column({ name: 'partyA', length: 255, type: 'varchar' }) + @ApiProperty({ description: '甲方' }) + partyA: string; + + // @Column({ type: 'tinyint', nullable: true, default: 1 }) + // @ApiProperty({ description: '状态:1启用,0禁用' }) + // status: number + + // @Column({ nullable: true }) + // @ApiProperty({ description: '是否默认用户' }) + // default: boolean + + // @ApiHideProperty() + // @ManyToMany(() => MenuEntity, menu => menu.roles, {}) + // @JoinTable({ + // name: 'sys_role_menus', + // joinColumn: { name: 'role_id', referencedColumnName: 'id' }, + // inverseJoinColumn: { name: 'menu_id', referencedColumnName: 'id' }, + // }) + // menus: Relation +} diff --git a/src/modules/contract/contract.module.ts b/src/modules/contract/contract.module.ts new file mode 100644 index 0000000..2dab2b4 --- /dev/null +++ b/src/modules/contract/contract.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { ContractController } from './contract.controller'; +import { ContractService } from './contract.service'; + +@Module({ + controllers: [ContractController], + providers: [ContractService], +}) +export class ContractModule {} diff --git a/src/modules/contract/contract.service.ts b/src/modules/contract/contract.service.ts new file mode 100644 index 0000000..87c091d --- /dev/null +++ b/src/modules/contract/contract.service.ts @@ -0,0 +1,4 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class ContractService {} diff --git a/src/modules/health/health.controller.ts b/src/modules/health/health.controller.ts index ad81caa..372e19b 100644 --- a/src/modules/health/health.controller.ts +++ b/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 { DiskHealthIndicator, HealthCheck, HttpHealthIndicator, MemoryHealthIndicator, TypeOrmHealthIndicator, -} from '@nestjs/terminus' +} from '@nestjs/terminus'; -import { Perm, definePermission } from '../auth/decorators/permission.decorator' +import { Perm, definePermission } from '../auth/decorators/permission.decorator'; export const PermissionHealth = definePermission('app:health', { NETWORK: 'network', @@ -16,7 +16,7 @@ export const PermissionHealth = definePermission('app:health', { MH: 'memory-heap', MR: 'memory-rss', DISK: 'disk', -} as const) +} as const); @ApiTags('Health - 健康检查') @Controller('health') @@ -25,21 +25,21 @@ export class HealthController { private http: HttpHealthIndicator, private db: TypeOrmHealthIndicator, private memory: MemoryHealthIndicator, - private disk: DiskHealthIndicator, + private disk: DiskHealthIndicator ) {} @Get('network') @HealthCheck() @Perm(PermissionHealth.NETWORK) async checkNetwork() { - return this.http.pingCheck('buqiyuan', 'https://buqiyuan.gitee.io/') + return this.http.pingCheck('louis', 'https://gitee.com/lu-zixun'); } @Get('database') @HealthCheck() @Perm(PermissionHealth.DB) async checkDatabase() { - return this.db.pingCheck('database') + return this.db.pingCheck('database'); } @Get('memory-heap') @@ -47,7 +47,7 @@ export class HealthController { @Perm(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 { @Perm(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/src/modules/health/health.module.ts b/src/modules/health/health.module.ts index 141fadc..e488987 100644 --- a/src/modules/health/health.module.ts +++ b/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/src/modules/netdisk/manager/manage.class.ts b/src/modules/netdisk/manager/manage.class.ts index ef774c1..d2dfa0b 100644 --- a/src/modules/netdisk/manager/manage.class.ts +++ b/src/modules/netdisk/manager/manage.class.ts @@ -49,8 +49,7 @@ export class SFileInfoDetail { mimeType: string; @ApiProperty({ - description: - '文件存储类型,2 表示归档存储,1 表示低频存储,0表示普通存储。', + description: '文件存储类型,2 表示归档存储,1 表示低频存储,0表示普通存储。', }) type: number; diff --git a/src/modules/netdisk/manager/manage.controller.ts b/src/modules/netdisk/manager/manage.controller.ts index 652d6aa..58e12e6 100644 --- a/src/modules/netdisk/manager/manage.controller.ts +++ b/src/modules/netdisk/manager/manage.controller.ts @@ -1,19 +1,15 @@ -import { Body, Controller, Get, Post, Query } from '@nestjs/common' -import { - ApiOkResponse, - ApiOperation, - ApiTags, -} from '@nestjs/swagger' +import { Body, Controller, Get, Post, Query } from '@nestjs/common'; +import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; -import { BusinessException } from '~/common/exceptions/biz.exception' -import { ErrorEnum } from '~/constants/error-code.constant' -import { AuthUser } from '~/modules/auth/decorators/auth-user.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 { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator' +import { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator'; -import { checkIsDemoMode } from '~/utils' +import { checkIsDemoMode } from '~/utils'; -import { SFileInfoDetail, SFileList, UploadToken } from './manage.class' +import { SFileInfoDetail, SFileList, UploadToken } from './manage.class'; import { DeleteDto, FileInfoDto, @@ -22,8 +18,8 @@ import { MKDirDto, MarkFileDto, RenameDto, -} from './manage.dto' -import { NetDiskManageService } from './manage.service' +} from './manage.dto'; +import { NetDiskManageService } from './manage.service'; export const permissions = definePermission('netdisk:manage', { LIST: 'list', @@ -38,7 +34,7 @@ export const permissions = definePermission('netdisk:manage', { RENAME: 'rename', CUT: 'cut', COPY: 'copy', -} as const) +} as const); @ApiTags('NetDiskManage - 网盘管理模块') @Controller('manage') @@ -50,20 +46,17 @@ export class NetDiskManageController { @ApiOkResponse({ type: SFileList }) @Perm(permissions.LIST) async list(@Query() dto: GetFileListDto): Promise { - return await this.manageService.getFileList(dto.path, dto.marker, dto.key) + return await this.manageService.getFileList(dto.path, dto.marker, dto.key); } @Post('mkdir') @ApiOperation({ summary: '创建文件夹,支持多级' }) @Perm(permissions.MKDIR) async mkdir(@Body() dto: MKDirDto): Promise { - const result = await this.manageService.checkFileExist( - `${dto.path}${dto.dirName}/`, - ) - if (result) - throw new BusinessException(ErrorEnum.OSS_FILE_OR_DIR_EXIST) + const result = await this.manageService.checkFileExist(`${dto.path}${dto.dirName}/`); + if (result) throw new BusinessException(ErrorEnum.OSS_FILE_OR_DIR_EXIST); - await this.manageService.createDir(`${dto.path}${dto.dirName}`) + await this.manageService.createDir(`${dto.path}${dto.dirName}`); } @Get('token') @@ -71,11 +64,11 @@ export class NetDiskManageController { @ApiOkResponse({ type: UploadToken }) @Perm(permissions.TOKEN) async token(@AuthUser() user: IAuthUser): Promise { - checkIsDemoMode() + checkIsDemoMode(); return { token: this.manageService.createUploadToken(`${user.uid}`), - } + }; } @Get('info') @@ -83,7 +76,7 @@ export class NetDiskManageController { @ApiOkResponse({ type: SFileInfoDetail }) @Perm(permissions.INFO) async info(@Query() dto: FileInfoDto): Promise { - return await this.manageService.getFileInfo(dto.name, dto.path) + return await this.manageService.getFileInfo(dto.name, dto.path); } @Post('mark') @@ -92,7 +85,7 @@ export class NetDiskManageController { async mark(@Body() dto: MarkFileDto): Promise { await this.manageService.changeFileHeaders(dto.name, dto.path, { mark: dto.mark, - }) + }); } @Get('download') @@ -100,7 +93,7 @@ export class NetDiskManageController { @ApiOkResponse({ type: String }) @Perm(permissions.DOWNLOAD) async download(@Query() dto: FileInfoDto): Promise { - return this.manageService.getDownloadLink(`${dto.path}${dto.name}`) + return this.manageService.getDownloadLink(`${dto.path}${dto.name}`); } @Post('rename') @@ -108,22 +101,19 @@ export class NetDiskManageController { @Perm(permissions.RENAME) async rename(@Body() dto: RenameDto): Promise { const result = await this.manageService.checkFileExist( - `${dto.path}${dto.toName}${dto.type === 'dir' ? '/' : ''}`, - ) - if (result) - throw new BusinessException(ErrorEnum.OSS_FILE_OR_DIR_EXIST) + `${dto.path}${dto.toName}${dto.type === 'dir' ? '/' : ''}` + ); + if (result) throw new BusinessException(ErrorEnum.OSS_FILE_OR_DIR_EXIST); - if (dto.type === 'file') - await this.manageService.renameFile(dto.path, dto.name, dto.toName) - else - await this.manageService.renameDir(dto.path, dto.name, dto.toName) + if (dto.type === 'file') await this.manageService.renameFile(dto.path, dto.name, dto.toName); + else await this.manageService.renameDir(dto.path, dto.name, dto.toName); } @Post('delete') @ApiOperation({ summary: '删除文件或文件夹' }) @Perm(permissions.DELETE) async delete(@Body() dto: DeleteDto): Promise { - await this.manageService.deleteMultiFileOrDir(dto.files, dto.path) + await this.manageService.deleteMultiFileOrDir(dto.files, dto.path); } @Post('cut') @@ -131,23 +121,15 @@ export class NetDiskManageController { @Perm(permissions.CUT) async cut(@Body() dto: FileOpDto): Promise { if (dto.originPath === dto.toPath) - throw new BusinessException(ErrorEnum.OSS_NO_OPERATION_REQUIRED) + throw new BusinessException(ErrorEnum.OSS_NO_OPERATION_REQUIRED); - await this.manageService.moveMultiFileOrDir( - dto.files, - dto.originPath, - dto.toPath, - ) + await this.manageService.moveMultiFileOrDir(dto.files, dto.originPath, dto.toPath); } @Post('copy') @ApiOperation({ summary: '复制文件或文件夹,支持批量' }) @Perm(permissions.COPY) async copy(@Body() dto: FileOpDto): Promise { - await this.manageService.copyMultiFileOrDir( - dto.files, - dto.originPath, - dto.toPath, - ) + await this.manageService.copyMultiFileOrDir(dto.files, dto.originPath, dto.toPath); } } diff --git a/src/modules/netdisk/manager/manage.dto.ts b/src/modules/netdisk/manager/manage.dto.ts index 984ef9b..1b54c84 100644 --- a/src/modules/netdisk/manager/manage.dto.ts +++ b/src/modules/netdisk/manager/manage.dto.ts @@ -1,5 +1,5 @@ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' -import { Type } from 'class-transformer' +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; import { ArrayMaxSize, IsNotEmpty, @@ -12,31 +12,28 @@ import { ValidationArguments, ValidatorConstraint, ValidatorConstraintInterface, -} from 'class-validator' -import { isEmpty } from 'lodash' +} from 'class-validator'; +import { isEmpty } from 'lodash'; -import { NETDISK_HANDLE_MAX_ITEM } from '~/constants/oss.constant' +import { NETDISK_HANDLE_MAX_ITEM } from '~/constants/oss.constant'; @ValidatorConstraint({ name: 'IsLegalNameExpression', async: false }) export class IsLegalNameExpression implements ValidatorConstraintInterface { validate(value: string, args: ValidationArguments) { try { - if (isEmpty(value)) - throw new Error('dir name is empty') + if (isEmpty(value)) throw new Error('dir name is empty'); - if (value.includes('/')) - throw new Error('dir name not allow /') + if (value.includes('/')) throw new Error('dir name not allow /'); - return true - } - catch (e) { - return false + return true; + } catch (e) { + return false; } } defaultMessage(_args: ValidationArguments) { // here you can provide default error message if validation failed - return 'file or dir name invalid' + return 'file or dir name invalid'; } } @@ -44,30 +41,30 @@ export class FileOpItem { @ApiProperty({ description: '文件类型', enum: ['file', 'dir'] }) @IsString() @Matches(/(^file$)|(^dir$)/) - type: string + type: string; @ApiProperty({ description: '文件名称' }) @IsString() @IsNotEmpty() @Validate(IsLegalNameExpression) - name: string + name: string; } export class GetFileListDto { @ApiProperty({ description: '分页标识' }) @IsOptional() @IsString() - marker: string + marker: string; @ApiProperty({ description: '当前路径' }) @IsString() - path: string + path: string; @ApiPropertyOptional({ description: '搜索关键字' }) @Validate(IsLegalNameExpression) @ValidateIf(o => !isEmpty(o.key)) @IsString() - key: string + key: string; } export class MKDirDto { @@ -75,34 +72,34 @@ export class MKDirDto { @IsNotEmpty() @IsString() @Validate(IsLegalNameExpression) - dirName: string + dirName: string; @ApiProperty({ description: '所属路径' }) @IsString() - path: string + path: string; } export class RenameDto { @ApiProperty({ description: '文件类型' }) @IsString() @Matches(/(^file$)|(^dir$)/) - type: string + type: string; @ApiProperty({ description: '更改的名称' }) @IsString() @IsNotEmpty() @Validate(IsLegalNameExpression) - toName: string + toName: string; @ApiProperty({ description: '原来的名称' }) @IsString() @IsNotEmpty() @Validate(IsLegalNameExpression) - name: string + name: string; @ApiProperty({ description: '路径' }) @IsString() - path: string + path: string; } export class FileInfoDto { @@ -110,11 +107,11 @@ export class FileInfoDto { @IsString() @IsNotEmpty() @Validate(IsLegalNameExpression) - name: string + name: string; @ApiProperty({ description: '文件所在路径' }) @IsString() - path: string + path: string; } export class DeleteDto { @@ -122,11 +119,11 @@ export class DeleteDto { @Type(() => FileOpItem) @ArrayMaxSize(NETDISK_HANDLE_MAX_ITEM) @ValidateNested({ each: true }) - files: FileOpItem[] + files: FileOpItem[]; @ApiProperty({ description: '所在目录' }) @IsString() - path: string + path: string; } export class MarkFileDto { @@ -134,15 +131,15 @@ export class MarkFileDto { @IsString() @IsNotEmpty() @Validate(IsLegalNameExpression) - name: string + name: string; @ApiProperty({ description: '文件所在路径' }) @IsString() - path: string + path: string; @ApiProperty({ description: '备注信息' }) @IsString() - mark: string + mark: string; } export class FileOpDto { @@ -150,13 +147,13 @@ export class FileOpDto { @Type(() => FileOpItem) @ArrayMaxSize(NETDISK_HANDLE_MAX_ITEM) @ValidateNested({ each: true }) - files: FileOpItem[] + files: FileOpItem[]; @ApiProperty({ description: '操作前的目录' }) @IsString() - originPath: string + originPath: string; @ApiProperty({ description: '操作后的目录' }) @IsString() - toPath: string + toPath: string; } diff --git a/src/modules/netdisk/manager/manage.service.ts b/src/modules/netdisk/manager/manage.service.ts index 3574a65..9f639c7 100644 --- a/src/modules/netdisk/manager/manage.service.ts +++ b/src/modules/netdisk/manager/manage.service.ts @@ -1,45 +1,47 @@ -import { basename, extname } from 'node:path' +import { basename, extname } from 'node:path'; -import { Injectable } from '@nestjs/common' -import { ConfigService } from '@nestjs/config' -import { isEmpty } from 'lodash' -import * as qiniu from 'qiniu' -import { auth, conf, rs } from 'qiniu' +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { isEmpty } from 'lodash'; +import * as qiniu from 'qiniu'; +import { auth, conf, rs } from 'qiniu'; -import { ConfigKeyPaths } from '~/config' -import { NETDISK_COPY_SUFFIX, NETDISK_DELIMITER, NETDISK_HANDLE_MAX_ITEM, NETDISK_LIMIT } from '~/constants/oss.constant' +import { ConfigKeyPaths } from '~/config'; +import { + NETDISK_COPY_SUFFIX, + NETDISK_DELIMITER, + NETDISK_HANDLE_MAX_ITEM, + NETDISK_LIMIT, +} from '~/constants/oss.constant'; -import { AccountInfo } from '~/modules/user/user.model' -import { UserService } from '~/modules/user/user.service' +import { AccountInfo } from '~/modules/user/user.model'; +import { UserService } from '~/modules/user/user.service'; -import { generateRandomValue } from '~/utils' +import { generateRandomValue } from '~/utils'; -import { SFileInfo, SFileInfoDetail, SFileList } from './manage.class' -import { FileOpItem } from './manage.dto' +import { SFileInfo, SFileInfoDetail, SFileList } from './manage.class'; +import { FileOpItem } from './manage.dto'; @Injectable() export class NetDiskManageService { - private config: conf.ConfigOptions - private mac: auth.digest.Mac - private bucketManager: rs.BucketManager + private config: conf.ConfigOptions; + private mac: auth.digest.Mac; + private bucketManager: rs.BucketManager; private get qiniuConfig() { - return this.configService.get('oss', { infer: true }) + return this.configService.get('oss', { infer: true }); } constructor( private configService: ConfigService, - private userService: UserService, + private userService: UserService ) { - this.mac = new qiniu.auth.digest.Mac( - this.qiniuConfig.accessKey, - this.qiniuConfig.secretKey, - ) + this.mac = new qiniu.auth.digest.Mac(this.qiniuConfig.accessKey, this.qiniuConfig.secretKey); this.config = new qiniu.conf.Config({ zone: this.qiniuConfig.zone, - }) + }); // bucket manager - this.bucketManager = new qiniu.rs.BucketManager(this.mac, this.config) + this.bucketManager = new qiniu.rs.BucketManager(this.mac, this.config); } /** @@ -50,7 +52,7 @@ export class NetDiskManageService { */ async getFileList(prefix = '', marker = '', skey = ''): Promise { // 是否需要搜索 - const searching = !isEmpty(skey) + const searching = !isEmpty(skey); return new Promise((resolve, reject) => { this.bucketManager.listPrefix( this.qiniuConfig.bucket, @@ -62,28 +64,24 @@ export class NetDiskManageService { }, (err, respBody, respInfo) => { if (err) { - reject(err) - return + reject(err); + return; } if (respInfo.statusCode === 200) { // 如果这个nextMarker不为空,那么还有未列举完毕的文件列表,下次调用listPrefix的时候, // 指定options里面的marker为这个值 - const fileList: SFileInfo[] = [] + const fileList: SFileInfo[] = []; // 处理目录,但只有非搜索模式下可用 if (!searching && !isEmpty(respBody.commonPrefixes)) { // dir for (const dirPath of respBody.commonPrefixes) { - const name = (dirPath as string) - .substr(0, dirPath.length - 1) - .replace(prefix, '') + const name = (dirPath as string).substr(0, dirPath.length - 1).replace(prefix, ''); if (isEmpty(skey) || name.includes(skey)) { fileList.push({ - name: (dirPath as string) - .substr(0, dirPath.length - 1) - .replace(prefix, ''), + name: (dirPath as string).substr(0, dirPath.length - 1).replace(prefix, ''), type: 'dir', id: generateRandomValue(10), - }) + }); } } } @@ -93,23 +91,22 @@ export class NetDiskManageService { for (const item of respBody.items) { // 搜索模式下处理 if (searching) { - const pathList: string[] = item.key.split(NETDISK_DELIMITER) + const pathList: string[] = item.key.split(NETDISK_DELIMITER); // dir is empty stirng, file is key string - const name = pathList.pop() + const name = pathList.pop(); if ( - item.key.endsWith(NETDISK_DELIMITER) - && pathList[pathList.length - 1].includes(skey) + item.key.endsWith(NETDISK_DELIMITER) && + pathList[pathList.length - 1].includes(skey) ) { // 结果是目录 - const ditName = pathList.pop() + const ditName = pathList.pop(); fileList.push({ id: generateRandomValue(10), name: ditName, type: 'dir', belongTo: pathList.join(NETDISK_DELIMITER), - }) - } - else if (name.includes(skey)) { + }); + } else if (name.includes(skey)) { // 文件 fileList.push({ id: generateRandomValue(10), @@ -119,12 +116,11 @@ export class NetDiskManageService { mimeType: item.mimeType, putTime: new Date(Number.parseInt(item.putTime) / 10000), belongTo: pathList.join(NETDISK_DELIMITER), - }) + }); } - } - else { + } else { // 正常获取列表 - const fileKey = item.key.replace(prefix, '') as string + const fileKey = item.key.replace(prefix, '') as string; if (!isEmpty(fileKey)) { fileList.push({ id: generateRandomValue(10), @@ -133,7 +129,7 @@ export class NetDiskManageService { fsize: item.fsize, mimeType: item.mimeType, putTime: new Date(Number.parseInt(item.putTime) / 10000), - }) + }); } } } @@ -141,18 +137,15 @@ export class NetDiskManageService { resolve({ list: fileList, marker: respBody.marker || null, - }) - } - else { + }); + } else { reject( - new Error( - `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`, - ), - ) + new Error(`Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`) + ); } - }, - ) - }) + } + ); + }); } /** @@ -165,8 +158,8 @@ export class NetDiskManageService { `${path}${name}`, (err, respBody, respInfo) => { if (err) { - reject(err) - return + reject(err); + return; } if (respInfo.statusCode === 200) { const detailInfo: SFileInfoDetail = { @@ -178,34 +171,29 @@ export class NetDiskManageService { type: respBody.type, uploader: '', mark: respBody?.['x-qn-meta']?.['!mark'] ?? '', - } + }; if (!respBody.endUser) { - resolve(detailInfo) - } - else { + resolve(detailInfo); + } else { this.userService .getAccountInfo(Number.parseInt(respBody.endUser)) .then((user: AccountInfo) => { if (isEmpty(user)) { - resolve(detailInfo) + resolve(detailInfo); + } else { + detailInfo.uploader = user.username; + resolve(detailInfo); } - else { - detailInfo.uploader = user.username - resolve(detailInfo) - } - }) + }); } - } - else { + } else { reject( - new Error( - `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`, - ), - ) + new Error(`Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`) + ); } - }, - ) - }) + } + ); + }); } /** @@ -214,7 +202,7 @@ export class NetDiskManageService { async changeFileHeaders( name: string, path: string, - headers: { [k: string]: string }, + headers: { [k: string]: string } ): Promise { return new Promise((resolve, reject) => { this.bucketManager.changeHeaders( @@ -223,22 +211,19 @@ export class NetDiskManageService { headers, (err, _, respInfo) => { if (err) { - reject(err) - return + reject(err); + return; } if (respInfo.statusCode === 200) { - resolve() - } - else { + resolve(); + } else { reject( - new Error( - `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`, - ), - ) + new Error(`Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`) + ); } - }, - ) - }) + } + ); + }); } /** @@ -246,11 +231,11 @@ export class NetDiskManageService { * @returns true创建成功 */ async createDir(dirName: string): Promise { - const safeDirName = dirName.endsWith('/') ? dirName : `${dirName}/` + const safeDirName = dirName.endsWith('/') ? dirName : `${dirName}/`; return new Promise((resolve, reject) => { // 上传一个空文件以用于显示文件夹效果 - const formUploader = new qiniu.form_up.FormUploader(this.config) - const putExtra = new qiniu.form_up.PutExtra() + const formUploader = new qiniu.form_up.FormUploader(this.config); + const putExtra = new qiniu.form_up.PutExtra(); formUploader.put( this.createUploadToken(''), safeDirName, @@ -258,22 +243,19 @@ export class NetDiskManageService { putExtra, (respErr, respBody, respInfo) => { if (respErr) { - reject(respErr) - return + reject(respErr); + return; } if (respInfo.statusCode === 200) { - resolve() - } - else { + resolve(); + } else { reject( - new Error( - `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`, - ), - ) + new Error(`Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`) + ); } - }, - ) - }) + } + ); + }); } /** @@ -284,32 +266,24 @@ export class NetDiskManageService { // fix path end must a / // 检测文件夹是否存在 - this.bucketManager.stat( - this.qiniuConfig.bucket, - filePath, - (respErr, respBody, respInfo) => { - if (respErr) { - reject(respErr) - return - } - if (respInfo.statusCode === 200) { - // 文件夹存在 - resolve(true) - } - else if (respInfo.statusCode === 612) { - // 文件夹不存在 - resolve(false) - } - else { - reject( - new Error( - `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`, - ), - ) - } - }, - ) - }) + this.bucketManager.stat(this.qiniuConfig.bucket, filePath, (respErr, respBody, respInfo) => { + if (respErr) { + reject(respErr); + return; + } + if (respInfo.statusCode === 200) { + // 文件夹存在 + resolve(true); + } else if (respInfo.statusCode === 612) { + // 文件夹不存在 + resolve(false); + } else { + reject( + new Error(`Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`) + ); + } + }); + }); } /** @@ -322,9 +296,9 @@ export class NetDiskManageService { insertOnly: 1, fsizeLimit: 1024 ** 2 * 10, endUser, - }) - const uploadToken = policy.uploadToken(this.mac) - return uploadToken + }); + const uploadToken = policy.uploadToken(this.mac); + return uploadToken; } /** @@ -333,11 +307,11 @@ export class NetDiskManageService { * @param name 文件名称 */ async renameFile(dir: string, name: string, toName: string): Promise { - const fileName = `${dir}${name}` - const toFileName = `${dir}${toName}` + const fileName = `${dir}${name}`; + const toFileName = `${dir}${toName}`; const op = { force: true, - } + }; return new Promise((resolve, reject) => { this.bucketManager.move( this.qiniuConfig.bucket, @@ -347,34 +321,32 @@ export class NetDiskManageService { op, (err, respBody, respInfo) => { if (err) { - reject(err) - } - else { + reject(err); + } else { if (respInfo.statusCode === 200) { - resolve() - } - else { + resolve(); + } else { reject( new Error( - `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`, - ), - ) + `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}` + ) + ); } } - }, - ) - }) + } + ); + }); } /** * 移动文件 */ async moveFile(dir: string, toDir: string, name: string): Promise { - const fileName = `${dir}${name}` - const toFileName = `${toDir}${name}` + const fileName = `${dir}${name}`; + const toFileName = `${toDir}${name}`; const op = { force: true, - } + }; return new Promise((resolve, reject) => { this.bucketManager.move( this.qiniuConfig.bucket, @@ -384,37 +356,35 @@ export class NetDiskManageService { op, (err, respBody, respInfo) => { if (err) { - reject(err) - } - else { + reject(err); + } else { if (respInfo.statusCode === 200) { - resolve() - } - else { + resolve(); + } else { reject( new Error( - `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`, - ), - ) + `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}` + ) + ); } } - }, - ) - }) + } + ); + }); } /** * 复制文件 */ async copyFile(dir: string, toDir: string, name: string): Promise { - const fileName = `${dir}${name}` + const fileName = `${dir}${name}`; // 拼接文件名 - const ext = extname(name) - const bn = basename(name, ext) - const toFileName = `${toDir}${bn}${NETDISK_COPY_SUFFIX}${ext}` + const ext = extname(name); + const bn = basename(name, ext); + const toFileName = `${toDir}${bn}${NETDISK_COPY_SUFFIX}${ext}`; const op = { force: true, - } + }; return new Promise((resolve, reject) => { this.bucketManager.copy( this.qiniuConfig.bucket, @@ -424,37 +394,35 @@ export class NetDiskManageService { op, (err, respBody, respInfo) => { if (err) { - reject(err) - } - else { + reject(err); + } else { if (respInfo.statusCode === 200) { - resolve() - } - else { + resolve(); + } else { reject( new Error( - `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`, - ), - ) + `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}` + ) + ); } } - }, - ) - }) + } + ); + }); } /** * 重命名文件夹 */ async renameDir(path: string, name: string, toName: string): Promise { - const dirName = `${path}${name}` - const toDirName = `${path}${toName}` - let hasFile = true - let marker = '' + const dirName = `${path}${name}`; + const toDirName = `${path}${toName}`; + let hasFile = true; + let marker = ''; const op = { force: true, - } - const bucketName = this.qiniuConfig.bucket + }; + const bucketName = this.qiniuConfig.bucket; while (hasFile) { await new Promise((resolve, reject) => { // 列举当前目录下的所有文件 @@ -467,56 +435,43 @@ export class NetDiskManageService { }, (err, respBody, respInfo) => { if (err) { - reject(err) - return + reject(err); + return; } if (respInfo.statusCode === 200) { - const moveOperations = respBody.items.map((item) => { - const { key } = item - const destKey = key.replace(dirName, toDirName) - return qiniu.rs.moveOp( - bucketName, - key, - bucketName, - destKey, - op, - ) - }) - this.bucketManager.batch( - moveOperations, - (err2, respBody2, respInfo2) => { - if (err2) { - reject(err2) - return - } - if (respInfo2.statusCode === 200) { - if (isEmpty(respBody.marker)) - hasFile = false - else - marker = respBody.marker + const moveOperations = respBody.items.map(item => { + const { key } = item; + const destKey = key.replace(dirName, toDirName); + return qiniu.rs.moveOp(bucketName, key, bucketName, destKey, op); + }); + this.bucketManager.batch(moveOperations, (err2, respBody2, respInfo2) => { + if (err2) { + reject(err2); + return; + } + if (respInfo2.statusCode === 200) { + if (isEmpty(respBody.marker)) hasFile = false; + else marker = respBody.marker; - resolve() - } - else { - reject( - new Error( - `Qiniu Error Code: ${respInfo2.statusCode}, Info: ${respInfo2.statusMessage}`, - ), + resolve(); + } else { + reject( + new Error( + `Qiniu Error Code: ${respInfo2.statusCode}, Info: ${respInfo2.statusMessage}` ) - } - }, - ) - } - else { + ); + } + }); + } else { reject( new Error( - `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`, - ), - ) + `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}` + ) + ); } - }, - ) - }) + } + ); + }); } } @@ -527,16 +482,15 @@ export class NetDiskManageService { */ getDownloadLink(key: string): string { if (this.qiniuConfig.access === 'public') { - return this.bucketManager.publicDownloadUrl(this.qiniuConfig.domain, key) - } - else if (this.qiniuConfig.access === 'private') { + return this.bucketManager.publicDownloadUrl(this.qiniuConfig.domain, key); + } else if (this.qiniuConfig.access === 'private') { return this.bucketManager.privateDownloadUrl( this.qiniuConfig.domain, key, - Date.now() / 1000 + 36000, - ) + Date.now() / 1000 + 36000 + ); } - throw new Error('qiniu config access type not support') + throw new Error('qiniu config access type not support'); } /** @@ -551,22 +505,19 @@ export class NetDiskManageService { `${dir}${name}`, (err, respBody, respInfo) => { if (err) { - reject(err) - return + reject(err); + return; } if (respInfo.statusCode === 200) { - resolve() - } - else { + resolve(); + } else { reject( - new Error( - `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`, - ), - ) + new Error(`Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`) + ); } - }, - ) - }) + } + ); + }); } /** @@ -574,47 +525,40 @@ export class NetDiskManageService { * @param dir 文件夹所在的上级目录 * @param name 文件目录名称 */ - async deleteMultiFileOrDir( - fileList: FileOpItem[], - dir: string, - ): Promise { - const files = fileList.filter(item => item.type === 'file') + async deleteMultiFileOrDir(fileList: FileOpItem[], dir: string): Promise { + const files = fileList.filter(item => item.type === 'file'); if (files.length > 0) { // 批处理文件 - const copyOperations = files.map((item) => { - const fileName = `${dir}${item.name}` - return qiniu.rs.deleteOp(this.qiniuConfig.bucket, fileName) - }) + const copyOperations = files.map(item => { + const fileName = `${dir}${item.name}`; + return qiniu.rs.deleteOp(this.qiniuConfig.bucket, fileName); + }); await new Promise((resolve, reject) => { this.bucketManager.batch(copyOperations, (err, respBody, respInfo) => { if (err) { - reject(err) - return + reject(err); + return; } if (respInfo.statusCode === 200) { - resolve() - } - else if (respInfo.statusCode === 298) { - reject(new Error('操作异常,但部分文件夹删除成功')) - } - else { + resolve(); + } else if (respInfo.statusCode === 298) { + reject(new Error('操作异常,但部分文件夹删除成功')); + } else { reject( - new Error( - `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`, - ), - ) + new Error(`Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`) + ); } - }) - }) + }); + }); } // 处理文件夹 - const dirs = fileList.filter(item => item.type === 'dir') + const dirs = fileList.filter(item => item.type === 'dir'); if (dirs.length > 0) { // 处理文件夹的复制 for (let i = 0; i < dirs.length; i++) { - const dirName = `${dir}${dirs[i].name}/` - let hasFile = true - let marker = '' + const dirName = `${dir}${dirs[i].name}/`; + let hasFile = true; + let marker = ''; while (hasFile) { await new Promise((resolve, reject) => { // 列举当前目录下的所有文件 @@ -627,49 +571,42 @@ export class NetDiskManageService { }, (err, respBody, respInfo) => { if (err) { - reject(err) - return + reject(err); + return; } if (respInfo.statusCode === 200) { - const moveOperations = respBody.items.map((item) => { - const { key } = item - return qiniu.rs.deleteOp(this.qiniuConfig.bucket, key) - }) - this.bucketManager.batch( - moveOperations, - (err2, respBody2, respInfo2) => { - if (err2) { - reject(err2) - return - } - if (respInfo2.statusCode === 200) { - if (isEmpty(respBody.marker)) - hasFile = false - else - marker = respBody.marker + const moveOperations = respBody.items.map(item => { + const { key } = item; + return qiniu.rs.deleteOp(this.qiniuConfig.bucket, key); + }); + this.bucketManager.batch(moveOperations, (err2, respBody2, respInfo2) => { + if (err2) { + reject(err2); + return; + } + if (respInfo2.statusCode === 200) { + if (isEmpty(respBody.marker)) hasFile = false; + else marker = respBody.marker; - resolve() - } - else { - reject( - new Error( - `Qiniu Error Code: ${respInfo2.statusCode}, Info: ${respInfo2.statusMessage}`, - ), + resolve(); + } else { + reject( + new Error( + `Qiniu Error Code: ${respInfo2.statusCode}, Info: ${respInfo2.statusMessage}` ) - } - }, - ) - } - else { + ); + } + }); + } else { reject( new Error( - `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`, - ), - ) + `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}` + ) + ); } - }, - ) - }) + } + ); + }); } } } @@ -678,62 +615,54 @@ export class NetDiskManageService { /** * 复制文件,含文件夹 */ - async copyMultiFileOrDir( - fileList: FileOpItem[], - dir: string, - toDir: string, - ): Promise { - const files = fileList.filter(item => item.type === 'file') + async copyMultiFileOrDir(fileList: FileOpItem[], dir: string, toDir: string): Promise { + const files = fileList.filter(item => item.type === 'file'); const op = { force: true, - } + }; if (files.length > 0) { // 批处理文件 - const copyOperations = files.map((item) => { - const fileName = `${dir}${item.name}` + const copyOperations = files.map(item => { + const fileName = `${dir}${item.name}`; // 拼接文件名 - const ext = extname(item.name) - const bn = basename(item.name, ext) - const toFileName = `${toDir}${bn}${NETDISK_COPY_SUFFIX}${ext}` + const ext = extname(item.name); + const bn = basename(item.name, ext); + const toFileName = `${toDir}${bn}${NETDISK_COPY_SUFFIX}${ext}`; return qiniu.rs.copyOp( this.qiniuConfig.bucket, fileName, this.qiniuConfig.bucket, toFileName, - op, - ) - }) + op + ); + }); await new Promise((resolve, reject) => { this.bucketManager.batch(copyOperations, (err, respBody, respInfo) => { if (err) { - reject(err) - return + reject(err); + return; } if (respInfo.statusCode === 200) { - resolve() - } - else if (respInfo.statusCode === 298) { - reject(new Error('操作异常,但部分文件夹删除成功')) - } - else { + resolve(); + } else if (respInfo.statusCode === 298) { + reject(new Error('操作异常,但部分文件夹删除成功')); + } else { reject( - new Error( - `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`, - ), - ) + new Error(`Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`) + ); } - }) - }) + }); + }); } // 处理文件夹 - const dirs = fileList.filter(item => item.type === 'dir') + const dirs = fileList.filter(item => item.type === 'dir'); if (dirs.length > 0) { // 处理文件夹的复制 for (let i = 0; i < dirs.length; i++) { - const dirName = `${dir}${dirs[i].name}/` - const copyDirName = `${toDir}${dirs[i].name}${NETDISK_COPY_SUFFIX}/` - let hasFile = true - let marker = '' + const dirName = `${dir}${dirs[i].name}/`; + const copyDirName = `${toDir}${dirs[i].name}${NETDISK_COPY_SUFFIX}/`; + let hasFile = true; + let marker = ''; while (hasFile) { await new Promise((resolve, reject) => { // 列举当前目录下的所有文件 @@ -746,56 +675,49 @@ export class NetDiskManageService { }, (err, respBody, respInfo) => { if (err) { - reject(err) - return + reject(err); + return; } if (respInfo.statusCode === 200) { - const moveOperations = respBody.items.map((item) => { - const { key } = item - const destKey = key.replace(dirName, copyDirName) + const moveOperations = respBody.items.map(item => { + const { key } = item; + const destKey = key.replace(dirName, copyDirName); return qiniu.rs.copyOp( this.qiniuConfig.bucket, key, this.qiniuConfig.bucket, destKey, - op, - ) - }) - this.bucketManager.batch( - moveOperations, - (err2, respBody2, respInfo2) => { - if (err2) { - reject(err2) - return - } - if (respInfo2.statusCode === 200) { - if (isEmpty(respBody.marker)) - hasFile = false - else - marker = respBody.marker + op + ); + }); + this.bucketManager.batch(moveOperations, (err2, respBody2, respInfo2) => { + if (err2) { + reject(err2); + return; + } + if (respInfo2.statusCode === 200) { + if (isEmpty(respBody.marker)) hasFile = false; + else marker = respBody.marker; - resolve() - } - else { - reject( - new Error( - `Qiniu Error Code: ${respInfo2.statusCode}, Info: ${respInfo2.statusMessage}`, - ), + resolve(); + } else { + reject( + new Error( + `Qiniu Error Code: ${respInfo2.statusCode}, Info: ${respInfo2.statusMessage}` ) - } - }, - ) - } - else { + ); + } + }); + } else { reject( new Error( - `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`, - ), - ) + `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}` + ) + ); } - }, - ) - }) + } + ); + }); } } } @@ -804,63 +726,54 @@ export class NetDiskManageService { /** * 移动文件,含文件夹 */ - async moveMultiFileOrDir( - fileList: FileOpItem[], - dir: string, - toDir: string, - ): Promise { - const files = fileList.filter(item => item.type === 'file') + async moveMultiFileOrDir(fileList: FileOpItem[], dir: string, toDir: string): Promise { + const files = fileList.filter(item => item.type === 'file'); const op = { force: true, - } + }; if (files.length > 0) { // 批处理文件 - const copyOperations = files.map((item) => { - const fileName = `${dir}${item.name}` - const toFileName = `${toDir}${item.name}` + const copyOperations = files.map(item => { + const fileName = `${dir}${item.name}`; + const toFileName = `${toDir}${item.name}`; return qiniu.rs.moveOp( this.qiniuConfig.bucket, fileName, this.qiniuConfig.bucket, toFileName, - op, - ) - }) + op + ); + }); await new Promise((resolve, reject) => { this.bucketManager.batch(copyOperations, (err, respBody, respInfo) => { if (err) { - reject(err) - return + reject(err); + return; } if (respInfo.statusCode === 200) { - resolve() - } - else if (respInfo.statusCode === 298) { - reject(new Error('操作异常,但部分文件夹删除成功')) - } - else { + resolve(); + } else if (respInfo.statusCode === 298) { + reject(new Error('操作异常,但部分文件夹删除成功')); + } else { reject( - new Error( - `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`, - ), - ) + new Error(`Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`) + ); } - }) - }) + }); + }); } // 处理文件夹 - const dirs = fileList.filter(item => item.type === 'dir') + const dirs = fileList.filter(item => item.type === 'dir'); if (dirs.length > 0) { // 处理文件夹的复制 for (let i = 0; i < dirs.length; i++) { - const dirName = `${dir}${dirs[i].name}/` - const toDirName = `${toDir}${dirs[i].name}/` + const dirName = `${dir}${dirs[i].name}/`; + const toDirName = `${toDir}${dirs[i].name}/`; // 移动的目录不是是自己 - if (toDirName.startsWith(dirName)) - continue + if (toDirName.startsWith(dirName)) continue; - let hasFile = true - let marker = '' + let hasFile = true; + let marker = ''; while (hasFile) { await new Promise((resolve, reject) => { // 列举当前目录下的所有文件 @@ -873,56 +786,49 @@ export class NetDiskManageService { }, (err, respBody, respInfo) => { if (err) { - reject(err) - return + reject(err); + return; } if (respInfo.statusCode === 200) { - const moveOperations = respBody.items.map((item) => { - const { key } = item - const destKey = key.replace(dirName, toDirName) + const moveOperations = respBody.items.map(item => { + const { key } = item; + const destKey = key.replace(dirName, toDirName); return qiniu.rs.moveOp( this.qiniuConfig.bucket, key, this.qiniuConfig.bucket, destKey, - op, - ) - }) - this.bucketManager.batch( - moveOperations, - (err2, respBody2, respInfo2) => { - if (err2) { - reject(err2) - return - } - if (respInfo2.statusCode === 200) { - if (isEmpty(respBody.marker)) - hasFile = false - else - marker = respBody.marker + op + ); + }); + this.bucketManager.batch(moveOperations, (err2, respBody2, respInfo2) => { + if (err2) { + reject(err2); + return; + } + if (respInfo2.statusCode === 200) { + if (isEmpty(respBody.marker)) hasFile = false; + else marker = respBody.marker; - resolve() - } - else { - reject( - new Error( - `Qiniu Error Code: ${respInfo2.statusCode}, Info: ${respInfo2.statusMessage}`, - ), + resolve(); + } else { + reject( + new Error( + `Qiniu Error Code: ${respInfo2.statusCode}, Info: ${respInfo2.statusMessage}` ) - } - }, - ) - } - else { + ); + } + }); + } else { reject( new Error( - `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`, - ), - ) + `Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}` + ) + ); } - }, - ) - }) + } + ); + }); } } } diff --git a/src/modules/netdisk/netdisk.module.ts b/src/modules/netdisk/netdisk.module.ts index aa5a3f7..4883791 100644 --- a/src/modules/netdisk/netdisk.module.ts +++ b/src/modules/netdisk/netdisk.module.ts @@ -1,21 +1,24 @@ -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 { NetDiskManageController } from './manager/manage.controller' -import { NetDiskManageService } from './manager/manage.service' -import { NetDiskOverviewController } from './overview/overview.controller' -import { NetDiskOverviewService } from './overview/overview.service' +import { NetDiskManageController } from './manager/manage.controller'; +import { NetDiskManageService } from './manager/manage.service'; +import { NetDiskOverviewController } from './overview/overview.controller'; +import { NetDiskOverviewService } from './overview/overview.service'; @Module({ - imports: [UserModule, RouterModule.register([ - { - path: 'netdisk', - module: NetdiskModule, - }, - ])], + imports: [ + UserModule, + RouterModule.register([ + { + path: 'netdisk', + module: NetdiskModule, + }, + ]), + ], controllers: [NetDiskManageController, NetDiskOverviewController], providers: [NetDiskManageService, NetDiskOverviewService], }) diff --git a/src/modules/netdisk/overview/overview.controller.ts b/src/modules/netdisk/overview/overview.controller.ts index a7b7df9..acdfc9f 100644 --- a/src/modules/netdisk/overview/overview.controller.ts +++ b/src/modules/netdisk/overview/overview.controller.ts @@ -1,23 +1,15 @@ -import { CacheInterceptor, CacheKey, CacheTTL } from '@nestjs/cache-manager' -import { - Controller, - Get, - UseInterceptors, -} from '@nestjs/common' -import { - ApiOkResponse, - ApiOperation, - ApiTags, -} from '@nestjs/swagger' +import { CacheInterceptor, CacheKey, CacheTTL } from '@nestjs/cache-manager'; +import { Controller, Get, UseInterceptors } from '@nestjs/common'; +import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; -import { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator' +import { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator'; -import { OverviewSpaceInfo } from './overview.dto' -import { NetDiskOverviewService } from './overview.service' +import { OverviewSpaceInfo } from './overview.dto'; +import { NetDiskOverviewService } from './overview.service'; export const permissions = definePermission('netdisk:overview', { DESC: 'desc', -} as const) +} as const); @ApiTags('NetDiskOverview - 网盘概览模块') @Controller('overview') @@ -32,11 +24,11 @@ export class NetDiskOverviewController { @ApiOkResponse({ type: OverviewSpaceInfo }) @Perm(permissions.DESC) async space(): Promise { - const date = this.overviewService.getZeroHourAnd1Day(new Date()) - const hit = await this.overviewService.getHit(date) - const flow = await this.overviewService.getFlow(date) - const space = await this.overviewService.getSpace(date) - const count = await this.overviewService.getCount(date) + const date = this.overviewService.getZeroHourAnd1Day(new Date()); + const hit = await this.overviewService.getHit(date); + const flow = await this.overviewService.getFlow(date); + const space = await this.overviewService.getSpace(date); + const count = await this.overviewService.getCount(date); return { fileSize: count.datas[count.datas.length - 1], flowSize: flow.datas[flow.datas.length - 1], @@ -44,6 +36,6 @@ export class NetDiskOverviewController { spaceSize: space.datas[space.datas.length - 1], flowTrend: flow, sizeTrend: space, - } + }; } } diff --git a/src/modules/netdisk/overview/overview.service.ts b/src/modules/netdisk/overview/overview.service.ts index 40ff410..72e0387 100644 --- a/src/modules/netdisk/overview/overview.service.ts +++ b/src/modules/netdisk/overview/overview.service.ts @@ -1,35 +1,32 @@ -import { HttpService } from '@nestjs/axios' -import { Injectable } from '@nestjs/common' -import { ConfigService } from '@nestjs/config' -import dayjs from 'dayjs' -import * as qiniu from 'qiniu' +import { HttpService } from '@nestjs/axios'; +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import dayjs from 'dayjs'; +import * as qiniu from 'qiniu'; -import { ConfigKeyPaths } from '~/config' -import { OSS_API } from '~/constants/oss.constant' +import { ConfigKeyPaths } from '~/config'; +import { OSS_API } from '~/constants/oss.constant'; -import { CountInfo, FlowInfo, HitInfo, SpaceInfo } from './overview.dto' +import { CountInfo, FlowInfo, HitInfo, SpaceInfo } from './overview.dto'; @Injectable() export class NetDiskOverviewService { - private mac: qiniu.auth.digest.Mac - private readonly FORMAT = 'YYYYMMDDHHmmss' + private mac: qiniu.auth.digest.Mac; + private readonly FORMAT = 'YYYYMMDDHHmmss'; private get qiniuConfig() { - return this.configService.get('oss', { infer: true }) + return this.configService.get('oss', { infer: true }); } constructor( private configService: ConfigService, - private readonly httpService: HttpService, + private readonly httpService: HttpService ) { - this.mac = new qiniu.auth.digest.Mac( - this.qiniuConfig.accessKey, - this.qiniuConfig.secretKey, - ) + this.mac = new qiniu.auth.digest.Mac(this.qiniuConfig.accessKey, this.qiniuConfig.secretKey); } /** 获取格式化后的起始和结束时间 */ getStartAndEndDate(start: Date, end = new Date()) { - return [dayjs(start).format(this.FORMAT), dayjs(end).format(this.FORMAT)] + return [dayjs(start).format(this.FORMAT), dayjs(end).format(this.FORMAT)]; } /** @@ -40,9 +37,9 @@ export class NetDiskOverviewService { const defaultParams = { $bucket: this.qiniuConfig.bucket, g: 'day', - } - const searchParams = new URLSearchParams({ ...defaultParams, ...queryParams }) - return decodeURIComponent(`${OSS_API}/v6/${type}?${searchParams}`) + }; + const searchParams = new URLSearchParams({ ...defaultParams, ...queryParams }); + return decodeURIComponent(`${OSS_API}/v6/${type}?${searchParams}`); } /** 获取统计数据 */ @@ -51,33 +48,33 @@ export class NetDiskOverviewService { this.mac, url, 'GET', - 'application/x-www-form-urlencoded', - ) + 'application/x-www-form-urlencoded' + ); return this.httpService.axiosRef.get(url, { headers: { 'Content-Type': 'application/x-www-form-urlencoded', - 'Authorization': `${accessToken}`, + Authorization: `${accessToken}`, }, - }) + }); } /** * 获取当天零时 */ getZeroHourToDay(current: Date): Date { - const year = dayjs(current).year() - const month = dayjs(current).month() - const date = dayjs(current).date() - return new Date(year, month, date, 0) + const year = dayjs(current).year(); + const month = dayjs(current).month(); + const date = dayjs(current).date(); + return new Date(year, month, date, 0); } /** * 获取当月1号零时 */ getZeroHourAnd1Day(current: Date): Date { - const year = dayjs(current).year() - const month = dayjs(current).month() - return new Date(year, month, 1, 0) + const year = dayjs(current).year(); + const month = dayjs(current).month(); + return new Date(year, month, 1, 0); } /** @@ -85,15 +82,15 @@ export class NetDiskOverviewService { * https://developer.qiniu.com/kodo/3908/statistic-space */ async getSpace(beginDate: Date, endDate = new Date()): Promise { - const [begin, end] = this.getStartAndEndDate(beginDate, endDate) - const url = this.getStatisticUrl('space', { begin, end }) - const { data } = await this.getStatisticData(url) + const [begin, end] = this.getStartAndEndDate(beginDate, endDate); + const url = this.getStatisticUrl('space', { begin, end }); + const { data } = await this.getStatisticData(url); return { datas: data.datas, - times: data.times.map((e) => { - return dayjs.unix(e).date() + times: data.times.map(e => { + return dayjs.unix(e).date(); }), - } + }; } /** @@ -101,15 +98,15 @@ export class NetDiskOverviewService { * https://developer.qiniu.com/kodo/3914/count */ async getCount(beginDate: Date, endDate = new Date()): Promise { - const [begin, end] = this.getStartAndEndDate(beginDate, endDate) - const url = this.getStatisticUrl('count', { begin, end }) - const { data } = await this.getStatisticData(url) + const [begin, end] = this.getStartAndEndDate(beginDate, endDate); + const url = this.getStatisticUrl('count', { begin, end }); + const { data } = await this.getStatisticData(url); return { - times: data.times.map((e) => { - return dayjs.unix(e).date() + times: data.times.map(e => { + return dayjs.unix(e).date(); }), datas: data.datas, - } + }; } /** @@ -118,19 +115,25 @@ export class NetDiskOverviewService { * https://developer.qiniu.com/kodo/3820/blob-io */ async getFlow(beginDate: Date, endDate = new Date()): Promise { - const [begin, end] = this.getStartAndEndDate(beginDate, endDate) - const url = this.getStatisticUrl('blob_io', { begin, end, $ftype: 0, $src: 'origin', select: 'flow' }) - const { data } = await this.getStatisticData(url) - const times = [] - const datas = [] - data.forEach((e) => { - times.push(dayjs(e.time).date()) - datas.push(e.values.flow) - }) + const [begin, end] = this.getStartAndEndDate(beginDate, endDate); + const url = this.getStatisticUrl('blob_io', { + begin, + end, + $ftype: 0, + $src: 'origin', + select: 'flow', + }); + const { data } = await this.getStatisticData(url); + const times = []; + const datas = []; + data.forEach(e => { + times.push(dayjs(e.time).date()); + datas.push(e.values.flow); + }); return { times, datas, - } + }; } /** @@ -139,18 +142,24 @@ export class NetDiskOverviewService { * https://developer.qiniu.com/kodo/3820/blob-io */ async getHit(beginDate: Date, endDate = new Date()): Promise { - const [begin, end] = this.getStartAndEndDate(beginDate, endDate) - const url = this.getStatisticUrl('blob_io', { begin, end, $ftype: 0, $src: 'inner', select: 'hit' }) - const { data } = await this.getStatisticData(url) - const times = [] - const datas = [] - data.forEach((e) => { - times.push(dayjs(e.time).date()) - datas.push(e.values.hit) - }) + const [begin, end] = this.getStartAndEndDate(beginDate, endDate); + const url = this.getStatisticUrl('blob_io', { + begin, + end, + $ftype: 0, + $src: 'inner', + select: 'hit', + }); + const { data } = await this.getStatisticData(url); + const times = []; + const datas = []; + data.forEach(e => { + times.push(dayjs(e.time).date()); + datas.push(e.values.hit); + }); return { times, datas, - } + }; } } diff --git a/src/modules/sse/sse.controller.ts b/src/modules/sse/sse.controller.ts index 1e475b7..e9f0ecc 100644 --- a/src/modules/sse/sse.controller.ts +++ b/src/modules/sse/sse.controller.ts @@ -1,17 +1,25 @@ -import { BeforeApplicationShutdown, Controller, Param, ParseIntPipe, Req, Res, Sse } from '@nestjs/common' -import { ApiTags } from '@nestjs/swagger' -import { FastifyReply, FastifyRequest } from 'fastify' -import { Observable, interval } from 'rxjs' +import { + BeforeApplicationShutdown, + Controller, + Param, + ParseIntPipe, + Req, + Res, + Sse, +} from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; +import { FastifyReply, FastifyRequest } from 'fastify'; +import { Observable, interval } from 'rxjs'; -import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator' +import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator'; -import { MessageEvent, SseService } from './sse.service' +import { MessageEvent, SseService } from './sse.service'; @ApiTags('System - sse模块') @ApiSecurityAuth() @Controller('sse') export class SseController implements BeforeApplicationShutdown { - private replyMap: Map = new Map() + private replyMap: Map = new Map(); constructor(private readonly sseService: SseService) {} @@ -19,36 +27,40 @@ export class SseController implements BeforeApplicationShutdown { this.sseService.sendToAll({ type: 'close', data: 'bye~', - }) - this.replyMap.forEach((reply) => { - reply.raw.end().destroy() - }) + }); + this.replyMap.forEach(reply => { + reply.raw.end().destroy(); + }); } // 通过控制台关闭程序时触发 beforeApplicationShutdown() { // console.log('beforeApplicationShutdown') - this.closeAllConnect() + this.closeAllConnect(); } @Sse(':uid') - sse(@Param('uid', ParseIntPipe) uid: number, @Req() req: FastifyRequest, @Res() res: FastifyReply): Observable { - this.replyMap.set(uid, res) + sse( + @Param('uid', ParseIntPipe) uid: number, + @Req() req: FastifyRequest, + @Res() res: FastifyReply + ): Observable { + this.replyMap.set(uid, res); const subscription = interval(10000).subscribe(() => { - this.sseService.sendToClient(uid, { type: 'ping' }) - }) + this.sseService.sendToClient(uid, { type: 'ping' }); + }); // 当客户端断开连接时 req.raw.on('close', () => { - subscription.unsubscribe() - this.sseService.removeClient(uid) - this.replyMap.delete(uid) + subscription.unsubscribe(); + this.sseService.removeClient(uid); + this.replyMap.delete(uid); // console.log(`user-${uid}已关闭`) - }) + }); - return new Observable((subscriber) => { - this.sseService.addClient(uid, subscriber) - }) + return new Observable(subscriber => { + this.sseService.addClient(uid, subscriber); + }); } } diff --git a/src/modules/sse/sse.module.ts b/src/modules/sse/sse.module.ts index e6af2b1..62bcc39 100644 --- a/src/modules/sse/sse.module.ts +++ b/src/modules/sse/sse.module.ts @@ -1,7 +1,7 @@ -import { Module } from '@nestjs/common' +import { Module } from '@nestjs/common'; -import { SseController } from './sse.controller' -import { SseService } from './sse.service' +import { SseController } from './sse.controller'; +import { SseService } from './sse.service'; @Module({ imports: [], diff --git a/src/modules/sse/sse.service.ts b/src/modules/sse/sse.service.ts index 076e415..a514e36 100644 --- a/src/modules/sse/sse.service.ts +++ b/src/modules/sse/sse.service.ts @@ -1,42 +1,42 @@ -import { Injectable } from '@nestjs/common' -import { Subscriber } from 'rxjs' -import { In } from 'typeorm' +import { Injectable } from '@nestjs/common'; +import { Subscriber } from 'rxjs'; +import { In } from 'typeorm'; -import { ROOT_ROLE_ID } from '~/constants/system.constant' +import { ROOT_ROLE_ID } from '~/constants/system.constant'; -import { RoleEntity } from '~/modules/system/role/role.entity' -import { UserEntity } from '~/modules/user/user.entity' +import { RoleEntity } from '~/modules/system/role/role.entity'; +import { UserEntity } from '~/modules/user/user.entity'; export interface MessageEvent { - data?: string | object - id?: string - type?: 'ping' | 'close' | 'updatePermsAndMenus' - retry?: number + data?: string | object; + id?: string; + type?: 'ping' | 'close' | 'updatePermsAndMenus'; + retry?: number; } -const clientMap: Map> = new Map() +const clientMap: Map> = new Map(); @Injectable() export class SseService { addClient(uid: number, subscriber: Subscriber) { - clientMap.set(uid, subscriber) + clientMap.set(uid, subscriber); } removeClient(uid: number): void { - const client = clientMap.get(uid) - client?.complete() - clientMap.delete(uid) + const client = clientMap.get(uid); + client?.complete(); + clientMap.delete(uid); } sendToClient(uid: number, data: MessageEvent): void { - const client = clientMap.get(uid) - client?.next?.(data) + const client = clientMap.get(uid); + client?.next?.(data); } sendToAll(data: MessageEvent): void { - clientMap.forEach((client) => { - client.next(data) - }) + clientMap.forEach(client => { + client.next(data); + }); } /** @@ -45,10 +45,10 @@ export class SseService { * @constructor */ async noticeClientToUpdateMenusByUserIds(uid: number | number[]) { - const userIds = [].concat(uid) as number[] - userIds.forEach((uid) => { - this.sendToClient(uid, { type: 'updatePermsAndMenus' }) - }) + const userIds = [].concat(uid) as number[]; + userIds.forEach(uid => { + this.sendToClient(uid, { type: 'updatePermsAndMenus' }); + }); } /** @@ -61,9 +61,9 @@ export class SseService { id: In(menuIds), }, }, - }) - const roleIds = roleMenus.map(n => n.id).concat(ROOT_ROLE_ID) - await this.noticeClientToUpdateMenusByRoleIds(roleIds) + }); + const roleIds = roleMenus.map(n => n.id).concat(ROOT_ROLE_ID); + await this.noticeClientToUpdateMenusByRoleIds(roleIds); } /** @@ -76,10 +76,10 @@ export class SseService { id: In(roleIds), }, }, - }) + }); if (users) { - const userIds = users.map(n => n.id) - await this.noticeClientToUpdateMenusByUserIds(userIds) + const userIds = users.map(n => n.id); + await this.noticeClientToUpdateMenusByUserIds(userIds); } } } diff --git a/src/modules/system/dept/dept.controller.ts b/src/modules/system/dept/dept.controller.ts index 81b6ef9..06d28a8 100644 --- a/src/modules/system/dept/dept.controller.ts +++ b/src/modules/system/dept/dept.controller.ts @@ -1,17 +1,17 @@ -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 { BusinessException } from '~/common/exceptions/biz.exception' -import { ErrorEnum } from '~/constants/error-code.constant' -import { AuthUser } from '~/modules/auth/decorators/auth-user.decorator' -import { Perm, definePermission } 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 { Perm, definePermission } 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 = definePermission('system:dept', { LIST: 'list', @@ -19,7 +19,7 @@ export const permissions = definePermission('system:dept', { READ: 'read', UPDATE: 'update', DELETE: 'delete', -} as const) +} as const); @ApiSecurityAuth() @ApiTags('System - 部门模块') @@ -31,31 +31,29 @@ export class DeptController { @ApiOperation({ summary: '获取部门列表' }) @ApiResult({ type: [DeptEntity] }) @Perm(permissions.LIST) - async list( - @Query() dto: DeptQueryDto, @AuthUser('uid') uid: number): Promise { - return this.deptService.getDeptTree(uid, dto) + async list(@Query() dto: DeptQueryDto, @AuthUser('uid') uid: number): Promise { + return this.deptService.getDeptTree(uid, dto); } @Post() @ApiOperation({ summary: '创建部门' }) @Perm(permissions.CREATE) async create(@Body() dto: DeptDto): Promise { - await this.deptService.create(dto) + await this.deptService.create(dto); } @Get(':id') @ApiOperation({ summary: '查询部门信息' }) @Perm(permissions.READ) async info(@IdParam() id: number) { - return this.deptService.info(id) + return this.deptService.info(id); } @Put(':id') @ApiOperation({ summary: '更新部门' }) @Perm(permissions.UPDATE) - async update( - @IdParam() id: number, @Body() updateDeptDto: DeptDto): Promise { - await this.deptService.update(id, updateDeptDto) + async update(@IdParam() id: number, @Body() updateDeptDto: DeptDto): Promise { + await this.deptService.update(id, updateDeptDto); } @Delete(':id') @@ -63,16 +61,14 @@ export class DeptController { @Perm(permissions.DELETE) async delete(@IdParam() id: number): Promise { // 查询是否有关联用户或者部门,如果含有则无法删除 - const count = await this.deptService.countUserByDeptId(id) - if (count > 0) - throw new BusinessException(ErrorEnum.DEPARTMENT_HAS_ASSOCIATED_USERS) + const count = await this.deptService.countUserByDeptId(id); + if (count > 0) throw new BusinessException(ErrorEnum.DEPARTMENT_HAS_ASSOCIATED_USERS); - const count2 = await this.deptService.countChildDept(id) - console.log('count2', count2) - if (count2 > 0) - throw new BusinessException(ErrorEnum.DEPARTMENT_HAS_CHILD_DEPARTMENTS) + const count2 = await this.deptService.countChildDept(id); + console.log('count2', count2); + if (count2 > 0) throw new BusinessException(ErrorEnum.DEPARTMENT_HAS_CHILD_DEPARTMENTS); - await this.deptService.delete(id) + await this.deptService.delete(id); } // @Post('move') diff --git a/src/modules/system/dept/dept.dto.ts b/src/modules/system/dept/dept.dto.ts index 5d79782..14b15cf 100644 --- a/src/modules/system/dept/dept.dto.ts +++ b/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/src/modules/system/dept/dept.entity.ts b/src/modules/system/dept/dept.entity.ts index 3a69396..7092dbe 100644 --- a/src/modules/system/dept/dept.entity.ts +++ b/src/modules/system/dept/dept.entity.ts @@ -1,36 +1,28 @@ -import { ApiHideProperty, ApiProperty } from '@nestjs/swagger' -import { - Column, - Entity, - OneToMany, - Relation, - Tree, - TreeChildren, - TreeParent, -} from 'typeorm' +import { ApiHideProperty, ApiProperty } from '@nestjs/swagger'; +import { Column, Entity, OneToMany, Relation, Tree, TreeChildren, TreeParent } from 'typeorm'; -import { CommonEntity } from '~/common/entity/common.entity' +import { CommonEntity } from '~/common/entity/common.entity'; -import { UserEntity } from '../../user/user.entity' +import { UserEntity } from '../../user/user.entity'; @Entity({ name: 'sys_dept' }) @Tree('materialized-path') export class DeptEntity extends CommonEntity { @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 + parent?: DeptEntity; @ApiHideProperty() @OneToMany(() => UserEntity, user => user.dept) - users: Relation + users: Relation; } diff --git a/src/modules/system/dept/dept.module.ts b/src/modules/system/dept/dept.module.ts index d936ed9..aeade9c 100644 --- a/src/modules/system/dept/dept.module.ts +++ b/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/src/modules/system/dept/dept.service.ts b/src/modules/system/dept/dept.service.ts index 1eae39b..418eaf3 100644 --- a/src/modules/system/dept/dept.service.ts +++ b/src/modules/system/dept/dept.service.ts @@ -1,16 +1,16 @@ -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/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/user.entity'; -import { deleteEmptyChildren } from '~/utils/list2tree.util' +import { deleteEmptyChildren } from '~/utils/list2tree.util'; -import { DeptDto, DeptQueryDto, MoveDept } from './dept.dto' +import { DeptDto, DeptQueryDto, MoveDept } from './dept.dto'; @Injectable() export class DeptService { @@ -19,11 +19,11 @@ export class DeptService { private userRepository: Repository, @InjectRepository(DeptEntity) private deptRepository: TreeRepository, - @InjectEntityManager() private entityManager: EntityManager, + @InjectEntityManager() private entityManager: EntityManager ) {} async list(): Promise { - return this.deptRepository.find({ order: { orderNo: 'DESC' } }) + return this.deptRepository.find({ order: { orderNo: 'DESC' } }); } async info(id: number): Promise { @@ -31,104 +31,97 @@ export class DeptService { .createQueryBuilder('dept') .leftJoinAndSelect('dept.parent', 'parent') .where({ id }) - .getOne() + .getOne(); - if (isEmpty(dept)) - throw new BusinessException(ErrorEnum.DEPARTMENT_NOT_FOUND) + if (isEmpty(dept)) 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() + const item = await this.deptRepository.createQueryBuilder('dept').where({ id }).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); } /** * 移动排序 */ async move(depts: MoveDept[]): Promise { - await this.entityManager.transaction(async (manager) => { - await manager.save(depts) - }) + await this.entityManager.transaction(async manager => { + 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 (await this.deptRepository.countDescendants(item)) - 1 + const item = await this.deptRepository.findOneBy({ id }); + return (await this.deptRepository.countDescendants(item)) - 1; } /** * 获取部门列表树结构 */ - async getDeptTree( - uid: number, - { name }: DeptQueryDto, - ): Promise { - const tree: DeptEntity[] = [] + async getDeptTree(uid: number, { name }: DeptQueryDto): Promise { + 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/src/modules/system/dict-item/dict-item.controller.ts b/src/modules/system/dict-item/dict-item.controller.ts index 6906aa6..a0ac474 100644 --- a/src/modules/system/dict-item/dict-item.controller.ts +++ b/src/modules/system/dict-item/dict-item.controller.ts @@ -1,16 +1,16 @@ -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 { AuthUser } from '~/modules/auth/decorators/auth-user.decorator' -import { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator' -import { DictItemEntity } from '~/modules/system/dict-item/dict-item.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 { AuthUser } from '~/modules/auth/decorators/auth-user.decorator'; +import { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator'; +import { DictItemEntity } from '~/modules/system/dict-item/dict-item.entity'; -import { DictItemDto, DictItemQueryDto } from './dict-item.dto' -import { DictItemService } from './dict-item.service' +import { DictItemDto, DictItemQueryDto } from './dict-item.dto'; +import { DictItemService } from './dict-item.service'; export const permissions = definePermission('system:dict-item', { LIST: 'list', @@ -18,7 +18,7 @@ export const permissions = definePermission('system:dict-item', { READ: 'read', UPDATE: 'update', DELETE: 'delete', -} as const) +} as const); @ApiTags('System - 字典项模块') @ApiSecurityAuth() @@ -31,16 +31,16 @@ export class DictItemController { @ApiResult({ type: [DictItemEntity], isPage: true }) @Perm(permissions.LIST) async list(@Query() dto: DictItemQueryDto): Promise> { - return this.dictItemService.page(dto) + return this.dictItemService.page(dto); } @Post() @ApiOperation({ summary: '新增字典项' }) @Perm(permissions.CREATE) async create(@Body() dto: DictItemDto, @AuthUser() user: IAuthUser): Promise { - await this.dictItemService.isExistKey(dto) - dto.createBy = dto.updateBy = user.uid - await this.dictItemService.create(dto) + await this.dictItemService.isExistKey(dto); + dto.createBy = dto.updateBy = user.uid; + await this.dictItemService.create(dto); } @Get(':id') @@ -48,21 +48,25 @@ export class DictItemController { @ApiResult({ type: DictItemEntity }) @Perm(permissions.READ) async info(@IdParam() id: number): Promise { - return this.dictItemService.findOne(id) + return this.dictItemService.findOne(id); } @Post(':id') @ApiOperation({ summary: '更新字典项' }) @Perm(permissions.UPDATE) - async update(@IdParam() id: number, @Body() dto: DictItemDto, @AuthUser() user: IAuthUser): Promise { - dto.updateBy = user.uid - await this.dictItemService.update(id, dto) + async update( + @IdParam() id: number, + @Body() dto: DictItemDto, + @AuthUser() user: IAuthUser + ): Promise { + dto.updateBy = user.uid; + await this.dictItemService.update(id, dto); } @Delete(':id') @ApiOperation({ summary: '删除指定的字典项' }) @Perm(permissions.DELETE) async delete(@IdParam() id: number): Promise { - await this.dictItemService.delete(id) + await this.dictItemService.delete(id); } } diff --git a/src/modules/system/dict-item/dict-item.dto.ts b/src/modules/system/dict-item/dict-item.dto.ts index fa5cf65..5bcd7d5 100644 --- a/src/modules/system/dict-item/dict-item.dto.ts +++ b/src/modules/system/dict-item/dict-item.dto.ts @@ -1,48 +1,48 @@ -import { ApiProperty, PartialType } from '@nestjs/swagger' -import { IsInt, IsOptional, IsString, MinLength } from 'class-validator' +import { ApiProperty, PartialType } from '@nestjs/swagger'; +import { IsInt, IsOptional, IsString, MinLength } from 'class-validator'; -import { PagerDto } from '~/common/dto/pager.dto' +import { PagerDto } from '~/common/dto/pager.dto'; -import { DictItemEntity } from './dict-item.entity' +import { DictItemEntity } from './dict-item.entity'; export class DictItemDto extends PartialType(DictItemEntity) { @ApiProperty({ description: '字典类型 ID' }) @IsInt() - typeId: number + typeId: number; @ApiProperty({ description: '字典项键名' }) @IsString() @MinLength(1) - label: string + label: string; @ApiProperty({ description: '字典项值' }) @IsString() @MinLength(1) - value: string + value: string; @ApiProperty({ description: '状态' }) @IsOptional() @IsInt() - status?: number + status?: number; @ApiProperty({ description: '备注' }) @IsOptional() @IsString() - remark?: string + remark?: string; } export class DictItemQueryDto extends PagerDto { @ApiProperty({ description: '字典类型 ID', required: true }) @IsInt() - typeId: number + typeId: number; @ApiProperty({ description: '字典项键名' }) @IsString() @IsOptional() - label?: string + label?: string; @ApiProperty({ description: '字典项值' }) @IsString() @IsOptional() - value?: string + value?: string; } diff --git a/src/modules/system/dict-item/dict-item.entity.ts b/src/modules/system/dict-item/dict-item.entity.ts index d4885a8..523f4d0 100644 --- a/src/modules/system/dict-item/dict-item.entity.ts +++ b/src/modules/system/dict-item/dict-item.entity.ts @@ -1,32 +1,32 @@ -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 { CompleteEntity } from '~/common/entity/common.entity' +import { CompleteEntity } from '~/common/entity/common.entity'; -import { DictTypeEntity } from '../dict-type/dict-type.entity' +import { DictTypeEntity } from '../dict-type/dict-type.entity'; @Entity({ name: 'sys_dict_item' }) export class DictItemEntity extends CompleteEntity { @ManyToOne(() => DictTypeEntity, { onDelete: 'CASCADE' }) @JoinColumn({ name: 'type_id' }) - type: DictTypeEntity + type: DictTypeEntity; @Column({ type: 'varchar', length: 50 }) @ApiProperty({ description: '字典项键名' }) - label: string + label: string; @Column({ type: 'varchar', length: 50 }) @ApiProperty({ description: '字典项值' }) - value: string + value: string; @Column({ nullable: true, comment: '字典项排序' }) - orderNo: number + orderNo: number; @Column({ type: 'tinyint', default: 1 }) @ApiProperty({ description: ' 状态' }) - status: number + status: number; @Column({ type: 'varchar', nullable: true }) @ApiProperty({ description: '备注' }) - remark: string + remark: string; } diff --git a/src/modules/system/dict-item/dict-item.module.ts b/src/modules/system/dict-item/dict-item.module.ts index e5ee832..824e0f9 100644 --- a/src/modules/system/dict-item/dict-item.module.ts +++ b/src/modules/system/dict-item/dict-item.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 { DictItemController } from './dict-item.controller' -import { DictItemEntity } from './dict-item.entity' -import { DictItemService } from './dict-item.service' +import { DictItemController } from './dict-item.controller'; +import { DictItemEntity } from './dict-item.entity'; +import { DictItemService } from './dict-item.service'; -const services = [DictItemService] +const services = [DictItemService]; @Module({ imports: [TypeOrmModule.forFeature([DictItemEntity])], diff --git a/src/modules/system/dict-item/dict-item.service.ts b/src/modules/system/dict-item/dict-item.service.ts index ae5f0a0..295cb60 100644 --- a/src/modules/system/dict-item/dict-item.service.ts +++ b/src/modules/system/dict-item/dict-item.service.ts @@ -1,21 +1,21 @@ -import { Injectable } from '@nestjs/common' -import { InjectRepository } from '@nestjs/typeorm' +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; -import { Like, Repository } from 'typeorm' +import { Like, 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 { DictItemEntity } from '~/modules/system/dict-item/dict-item.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 { DictItemEntity } from '~/modules/system/dict-item/dict-item.entity'; -import { DictItemDto, DictItemQueryDto } from './dict-item.dto' +import { DictItemDto, DictItemQueryDto } from './dict-item.dto'; @Injectable() export class DictItemService { constructor( @InjectRepository(DictItemEntity) - private dictItemRepository: Repository, + private dictItemRepository: Repository ) {} /** @@ -28,7 +28,8 @@ export class DictItemService { value, typeId, }: DictItemQueryDto): Promise> { - const queryBuilder = this.dictItemRepository.createQueryBuilder('dict_item') + const queryBuilder = this.dictItemRepository + .createQueryBuilder('dict_item') .orderBy({ orderNo: 'ASC' }) .where({ ...(label && { label: Like(`%${label}%`) }), @@ -36,62 +37,61 @@ export class DictItemService { type: { id: typeId, }, - }) + }); - return paginate(queryBuilder, { page, pageSize }) + return paginate(queryBuilder, { page, pageSize }); } /** * 获取参数总数 */ async countConfigList(): Promise { - return this.dictItemRepository.count() + return this.dictItemRepository.count(); } /** * 新增 */ async create(dto: DictItemDto): Promise { - const { typeId, ...rest } = dto + const { typeId, ...rest } = dto; await this.dictItemRepository.insert({ ...rest, type: { id: typeId, }, - }) + }); } /** * 更新 */ async update(id: number, dto: Partial): Promise { - const { typeId, ...rest } = dto + const { typeId, ...rest } = dto; await this.dictItemRepository.update(id, { ...rest, type: { id: typeId, }, - }) + }); } /** * 删除 */ async delete(id: number): Promise { - await this.dictItemRepository.delete(id) + await this.dictItemRepository.delete(id); } /** * 查询单个 */ async findOne(id: number): Promise { - return this.dictItemRepository.findOneBy({ id }) + return this.dictItemRepository.findOneBy({ id }); } async isExistKey(dto: DictItemDto): Promise { - const { value, typeId } = dto - const result = await this.dictItemRepository.findOneBy({ value, type: { id: typeId } }) - if (result) - throw new BusinessException(ErrorEnum.DICT_NAME_EXISTS) + const { value, typeId } = dto; + const result = await this.dictItemRepository.findOneBy({ value, type: { id: typeId } }); + if (result) throw new BusinessException(ErrorEnum.DICT_NAME_EXISTS); } } diff --git a/src/modules/system/dict-type/dict-type.controller.ts b/src/modules/system/dict-type/dict-type.controller.ts index 652a94b..4d98575 100644 --- a/src/modules/system/dict-type/dict-type.controller.ts +++ b/src/modules/system/dict-type/dict-type.controller.ts @@ -1,16 +1,16 @@ -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 { AuthUser } from '~/modules/auth/decorators/auth-user.decorator' -import { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator' -import { DictTypeEntity } from '~/modules/system/dict-type/dict-type.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 { AuthUser } from '~/modules/auth/decorators/auth-user.decorator'; +import { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator'; +import { DictTypeEntity } from '~/modules/system/dict-type/dict-type.entity'; -import { DictTypeDto, DictTypeQueryDto } from './dict-type.dto' -import { DictTypeService } from './dict-type.service' +import { DictTypeDto, DictTypeQueryDto } from './dict-type.dto'; +import { DictTypeService } from './dict-type.service'; export const permissions = definePermission('system:dict-type', { LIST: 'list', @@ -18,7 +18,7 @@ export const permissions = definePermission('system:dict-type', { READ: 'read', UPDATE: 'update', DELETE: 'delete', -} as const) +} as const); @ApiTags('System - 字典类型模块') @ApiSecurityAuth() @@ -31,7 +31,7 @@ export class DictTypeController { @ApiResult({ type: [DictTypeEntity], isPage: true }) @Perm(permissions.LIST) async list(@Query() dto: DictTypeQueryDto): Promise> { - return this.dictTypeService.page(dto) + return this.dictTypeService.page(dto); } @Get('select-options') @@ -39,16 +39,16 @@ export class DictTypeController { @ApiResult({ type: [DictTypeEntity] }) @Perm(permissions.LIST) async getAll(): Promise { - return this.dictTypeService.getAll() + return this.dictTypeService.getAll(); } @Post() @ApiOperation({ summary: '新增字典类型' }) @Perm(permissions.CREATE) async create(@Body() dto: DictTypeDto, @AuthUser() user: IAuthUser): Promise { - await this.dictTypeService.isExistKey(dto.name) - dto.createBy = dto.updateBy = user.uid - await this.dictTypeService.create(dto) + await this.dictTypeService.isExistKey(dto.name); + dto.createBy = dto.updateBy = user.uid; + await this.dictTypeService.create(dto); } @Get(':id') @@ -56,21 +56,25 @@ export class DictTypeController { @ApiResult({ type: DictTypeEntity }) @Perm(permissions.READ) async info(@IdParam() id: number): Promise { - return this.dictTypeService.findOne(id) + return this.dictTypeService.findOne(id); } @Post(':id') @ApiOperation({ summary: '更新字典类型' }) @Perm(permissions.UPDATE) - async update(@IdParam() id: number, @Body() dto: DictTypeDto, @AuthUser() user: IAuthUser): Promise { - dto.updateBy = user.uid - await this.dictTypeService.update(id, dto) + async update( + @IdParam() id: number, + @Body() dto: DictTypeDto, + @AuthUser() user: IAuthUser + ): Promise { + dto.updateBy = user.uid; + await this.dictTypeService.update(id, dto); } @Delete(':id') @ApiOperation({ summary: '删除指定的字典类型' }) @Perm(permissions.DELETE) async delete(@IdParam() id: number): Promise { - await this.dictTypeService.delete(id) + await this.dictTypeService.delete(id); } } diff --git a/src/modules/system/dict-type/dict-type.dto.ts b/src/modules/system/dict-type/dict-type.dto.ts index c3809b4..010479f 100644 --- a/src/modules/system/dict-type/dict-type.dto.ts +++ b/src/modules/system/dict-type/dict-type.dto.ts @@ -1,40 +1,40 @@ -import { ApiProperty, PartialType } from '@nestjs/swagger' -import { IsInt, IsOptional, IsString, MinLength } from 'class-validator' +import { ApiProperty, PartialType } from '@nestjs/swagger'; +import { IsInt, IsOptional, IsString, MinLength } from 'class-validator'; -import { PagerDto } from '~/common/dto/pager.dto' +import { PagerDto } from '~/common/dto/pager.dto'; -import { DictTypeEntity } from './dict-type.entity' +import { DictTypeEntity } from './dict-type.entity'; export class DictTypeDto extends PartialType(DictTypeEntity) { @ApiProperty({ description: '字典类型名称' }) @IsString() @MinLength(1) - name: string + name: string; @ApiProperty({ description: '字典类型code' }) @IsString() @MinLength(3) - code: string + code: string; @ApiProperty({ description: '状态' }) @IsOptional() @IsInt() - status?: number + status?: number; @ApiProperty({ description: '备注' }) @IsOptional() @IsString() - remark?: string + remark?: string; } export class DictTypeQueryDto extends PagerDto { @ApiProperty({ description: '字典类型名称' }) @IsString() @IsOptional() - name: string + name: string; @ApiProperty({ description: '字典类型code' }) @IsString() @IsOptional() - code: string + code: string; } diff --git a/src/modules/system/dict-type/dict-type.entity.ts b/src/modules/system/dict-type/dict-type.entity.ts index 8d2608f..e4f0e8c 100644 --- a/src/modules/system/dict-type/dict-type.entity.ts +++ b/src/modules/system/dict-type/dict-type.entity.ts @@ -1,23 +1,23 @@ -import { ApiProperty } from '@nestjs/swagger' -import { Column, Entity } from 'typeorm' +import { ApiProperty } from '@nestjs/swagger'; +import { Column, Entity } from 'typeorm'; -import { CompleteEntity } from '~/common/entity/common.entity' +import { CompleteEntity } from '~/common/entity/common.entity'; @Entity({ name: 'sys_dict_type' }) export class DictTypeEntity extends CompleteEntity { @Column({ type: 'varchar', length: 50 }) @ApiProperty({ description: '字典名称' }) - name: string + name: string; @Column({ type: 'varchar', length: 50, unique: true }) @ApiProperty({ description: '字典类型' }) - code: string + code: string; @Column({ type: 'tinyint', default: 1 }) @ApiProperty({ description: ' 状态' }) - status: number + status: number; @Column({ type: 'varchar', nullable: true }) @ApiProperty({ description: '备注' }) - remark: string + remark: string; } diff --git a/src/modules/system/dict-type/dict-type.module.ts b/src/modules/system/dict-type/dict-type.module.ts index 5f8f238..b5ad211 100644 --- a/src/modules/system/dict-type/dict-type.module.ts +++ b/src/modules/system/dict-type/dict-type.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 { DictTypeController } from './dict-type.controller' -import { DictTypeEntity } from './dict-type.entity' -import { DictTypeService } from './dict-type.service' +import { DictTypeController } from './dict-type.controller'; +import { DictTypeEntity } from './dict-type.entity'; +import { DictTypeService } from './dict-type.service'; -const services = [DictTypeService] +const services = [DictTypeService]; @Module({ imports: [TypeOrmModule.forFeature([DictTypeEntity])], diff --git a/src/modules/system/dict-type/dict-type.service.ts b/src/modules/system/dict-type/dict-type.service.ts index 04e6c47..2b730fc 100644 --- a/src/modules/system/dict-type/dict-type.service.ts +++ b/src/modules/system/dict-type/dict-type.service.ts @@ -1,21 +1,21 @@ -import { Injectable } from '@nestjs/common' -import { InjectRepository } from '@nestjs/typeorm' +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; -import { Like, Repository } from 'typeorm' +import { Like, 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 { DictTypeEntity } from '~/modules/system/dict-type/dict-type.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 { DictTypeEntity } from '~/modules/system/dict-type/dict-type.entity'; -import { DictTypeDto, DictTypeQueryDto } from './dict-type.dto' +import { DictTypeDto, DictTypeQueryDto } from './dict-type.dto'; @Injectable() export class DictTypeService { constructor( @InjectRepository(DictTypeEntity) - private dictTypeRepository: Repository, + private dictTypeRepository: Repository ) {} /** @@ -27,58 +27,56 @@ export class DictTypeService { name, code, }: DictTypeQueryDto): Promise> { - const queryBuilder = this.dictTypeRepository.createQueryBuilder('dict_type') - .where({ - ...(name && { name: Like(`%${name}%`) }), - ...(code && { code: Like(`%${code}%`) }), - }) + const queryBuilder = this.dictTypeRepository.createQueryBuilder('dict_type').where({ + ...(name && { name: Like(`%${name}%`) }), + ...(code && { code: Like(`%${code}%`) }), + }); - return paginate(queryBuilder, { page, pageSize }) + return paginate(queryBuilder, { page, pageSize }); } /** 一次性获取所有的字典类型 */ async getAll() { - return this.dictTypeRepository.find() + return this.dictTypeRepository.find(); } /** * 获取参数总数 */ async countConfigList(): Promise { - return this.dictTypeRepository.count() + return this.dictTypeRepository.count(); } /** * 新增 */ async create(dto: DictTypeDto): Promise { - await this.dictTypeRepository.insert(dto) + await this.dictTypeRepository.insert(dto); } /** * 更新 */ async update(id: number, dto: Partial): Promise { - await this.dictTypeRepository.update(id, dto) + await this.dictTypeRepository.update(id, dto); } /** * 删除 */ async delete(id: number): Promise { - await this.dictTypeRepository.delete(id) + await this.dictTypeRepository.delete(id); } /** * 查询单个 */ async findOne(id: number): Promise { - return this.dictTypeRepository.findOneBy({ id }) + return this.dictTypeRepository.findOneBy({ id }); } async isExistKey(name: string): Promise { - const result = await this.dictTypeRepository.findOneBy({ name }) - if (result) - throw new BusinessException(ErrorEnum.DICT_NAME_EXISTS) + const result = await this.dictTypeRepository.findOneBy({ name }); + if (result) throw new BusinessException(ErrorEnum.DICT_NAME_EXISTS); } } diff --git a/src/modules/system/log/dto/log.dto.ts b/src/modules/system/log/dto/log.dto.ts index 5cf0ff3..fa2f27e 100644 --- a/src/modules/system/log/dto/log.dto.ts +++ b/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 { PagerDto } from '~/common/dto/pager.dto' +import { PagerDto } from '~/common/dto/pager.dto'; export class LoginLogQueryDto extends PagerDto { @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 PagerDto { @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 PagerDto { @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/src/modules/system/log/entities/captcha-log.entity.ts b/src/modules/system/log/entities/captcha-log.entity.ts index 048bec8..a00bb5c 100644 --- a/src/modules/system/log/entities/captcha-log.entity.ts +++ b/src/modules/system/log/entities/captcha-log.entity.ts @@ -1,23 +1,23 @@ -import { ApiProperty } from '@nestjs/swagger' -import { Column, Entity } from 'typeorm' +import { ApiProperty } from '@nestjs/swagger'; +import { Column, Entity } from 'typeorm'; -import { CommonEntity } from '~/common/entity/common.entity' +import { CommonEntity } from '~/common/entity/common.entity'; @Entity({ name: 'sys_captcha_log' }) export class CaptchaLogEntity extends CommonEntity { @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'; } diff --git a/src/modules/system/log/entities/login-log.entity.ts b/src/modules/system/log/entities/login-log.entity.ts index 7a56568..25d5826 100644 --- a/src/modules/system/log/entities/login-log.entity.ts +++ b/src/modules/system/log/entities/login-log.entity.ts @@ -1,29 +1,29 @@ -import { ApiProperty } from '@nestjs/swagger' -import { Column, Entity, JoinColumn, ManyToOne, Relation } from 'typeorm' +import { ApiProperty } from '@nestjs/swagger'; +import { Column, Entity, JoinColumn, ManyToOne, Relation } from 'typeorm'; -import { CommonEntity } from '~/common/entity/common.entity' +import { CommonEntity } from '~/common/entity/common.entity'; -import { UserEntity } from '../../../user/user.entity' +import { UserEntity } from '../../../user/user.entity'; @Entity({ name: 'sys_login_log' }) export class LoginLogEntity extends CommonEntity { @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({ length: 500, nullable: true }) @ApiProperty({ description: '浏览器ua' }) - ua: string + ua: string; @ManyToOne(() => UserEntity, { onDelete: 'CASCADE' }) @JoinColumn({ name: 'user_id' }) - user: Relation + user: Relation; } diff --git a/src/modules/system/log/entities/task-log.entity.ts b/src/modules/system/log/entities/task-log.entity.ts index 1721d68..9d7325f 100644 --- a/src/modules/system/log/entities/task-log.entity.ts +++ b/src/modules/system/log/entities/task-log.entity.ts @@ -1,25 +1,25 @@ -import { ApiProperty } from '@nestjs/swagger' -import { Column, Entity, JoinColumn, ManyToOne, Relation } from 'typeorm' +import { ApiProperty } from '@nestjs/swagger'; +import { Column, Entity, JoinColumn, ManyToOne, Relation } from 'typeorm'; -import { CommonEntity } from '~/common/entity/common.entity' +import { CommonEntity } from '~/common/entity/common.entity'; -import { TaskEntity } from '../../task/task.entity' +import { TaskEntity } from '../../task/task.entity'; @Entity({ name: 'sys_task_log' }) export class TaskLogEntity extends CommonEntity { @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({ name: 'task_id' }) - task: Relation + task: Relation; } diff --git a/src/modules/system/log/log.controller.ts b/src/modules/system/log/log.controller.ts index 489f3cb..144de21 100644 --- a/src/modules/system/log/log.controller.ts +++ b/src/modules/system/log/log.controller.ts @@ -1,28 +1,24 @@ -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 { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator' +import { ApiResult } from '~/common/decorators/api-result.decorator'; +import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator'; +import { Pagination } from '~/helper/paginate/pagination'; +import { Perm, definePermission } 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' +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'; export const permissions = definePermission('system:log', { TaskList: 'task:list', LogList: 'login:list', CaptchaList: 'captcha:list', -} as const) +} as const); @ApiSecurityAuth() @ApiTags('System - 日志模块') @@ -31,17 +27,15 @@ export class LogController { constructor( private loginLogService: LoginLogService, private taskService: TaskLogService, - private captchaLogService: CaptchaLogService, + private captchaLogService: CaptchaLogService ) {} @Get('login/list') @ApiOperation({ summary: '查询登录日志列表' }) @ApiResult({ type: [LoginLogInfo], isPage: true }) @Perm(permissions.TaskList) - async loginLogPage( - @Query() dto: LoginLogQueryDto, - ): Promise> { - return this.loginLogService.list(dto) + async loginLogPage(@Query() dto: LoginLogQueryDto): Promise> { + return this.loginLogService.list(dto); } @Get('task/list') @@ -49,16 +43,14 @@ export class LogController { @ApiResult({ type: [TaskLogEntity], isPage: true }) @Perm(permissions.LogList) async taskList(@Query() dto: TaskLogQueryDto) { - return this.taskService.list(dto) + return this.taskService.list(dto); } @Get('captcha/list') @ApiOperation({ summary: '查询验证码日志列表' }) @ApiResult({ type: [CaptchaLogEntity], isPage: true }) @Perm(permissions.CaptchaList) - async captchaList( - @Query() dto: CaptchaLogQueryDto, - ): Promise> { - return this.captchaLogService.paginate(dto) + async captchaList(@Query() dto: CaptchaLogQueryDto): Promise> { + return this.captchaLogService.paginate(dto); } } diff --git a/src/modules/system/log/log.module.ts b/src/modules/system/log/log.module.ts index 27f9f04..7f1b5b8 100644 --- a/src/modules/system/log/log.module.ts +++ b/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/src/modules/system/log/models/log.model.ts b/src/modules/system/log/models/log.model.ts index f128be0..580f227 100644 --- a/src/modules/system/log/models/log.model.ts +++ b/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: 'admin' }) - username: string + username: string; @ApiProperty({ description: '登录时间', example: '2023-12-22 16:46:20.333843' }) - time: string + time: 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/src/modules/system/log/services/captcha-log.service.ts b/src/modules/system/log/services/captcha-log.service.ts index 5bc01e1..b45c04c 100644 --- a/src/modules/system/log/services/captcha-log.service.ts +++ b/src/modules/system/log/services/captcha-log.service.ts @@ -1,50 +1,50 @@ -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 { constructor( @InjectRepository(CaptchaLogEntity) - private captchaLogRepository: Repository, + private captchaLogRepository: Repository ) {} async create( account: string, code: string, provider: 'sms' | 'email', - uid?: number, + uid?: number ): Promise { await this.captchaLogRepository.save({ account, 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/src/modules/system/log/services/login-log.service.ts b/src/modules/system/log/services/login-log.service.ts index b3420de..256c78b 100644 --- a/src/modules/system/log/services/login-log.service.ts +++ b/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 { getIpAddress } from '~/utils/ip.util' +import { getIpAddress } from '~/utils/ip.util'; -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,41 +24,32 @@ async function parseLoginLog(e: any, parser: UAParser): Promise { browser: `${`${uaResult.browser.name ?? ''} `}${uaResult.browser.version}`, username: e.user_username, time: e.login_log_created_at, - } + }; } @Injectable() export class LoginLogService { constructor( @InjectRepository(LoginLogEntity) - private loginLogRepository: Repository, - + private loginLogRepository: Repository ) {} async create(uid: number, ip: string, ua: string): Promise { try { - const address = await getIpAddress(ip) + const address = await getIpAddress(ip); await this.loginLogRepository.save({ ip, ua, address, user: { id: uid }, - }) - } - catch (e) { - console.error(e) + }); + } catch (e) { + console.error(e); } } - async list({ - page, - pageSize, - username, - ip, - address, - time, - }: LoginLogQueryDto) { + async list({ page, pageSize, username, ip, address, time }: LoginLogQueryDto) { const queryBuilder = await this.loginLogRepository .createQueryBuilder('login_log') .innerJoinAndSelect('login_log.user', 'user') @@ -72,29 +63,27 @@ 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 loginLogInfos = await Promise.all( - items.map(item => parseLoginLog(item, parser)), - ) + 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/src/modules/system/log/services/task-log.service.ts b/src/modules/system/log/services/task-log.service.ts index b57410f..e0da14a 100644 --- a/src/modules/system/log/services/task-log.service.ts +++ b/src/modules/system/log/services/task-log.service.ts @@ -1,52 +1,47 @@ -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 { constructor( @InjectRepository(TaskLogEntity) - private taskLogRepository: Repository, + private taskLogRepository: Repository ) {} - async create( - tid: number, - status: number, - time?: number, - err?: string, - ): Promise { + async create(tid: number, status: number, time?: number, err?: string): Promise { const result = await this.taskLogRepository.save({ status, 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/src/modules/system/menu/menu.controller.ts b/src/modules/system/menu/menu.controller.ts index 2de0370..af30512 100644 --- a/src/modules/system/menu/menu.controller.ts +++ b/src/modules/system/menu/menu.controller.ts @@ -7,18 +7,22 @@ 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 { Perm, definePermission, getDefinePermissions } from '~/modules/auth/decorators/permission.decorator' +import { ApiResult } from '~/common/decorators/api-result.decorator'; +import { IdParam } from '~/common/decorators/id-param.decorator'; +import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator'; +import { + Perm, + definePermission, + getDefinePermissions, +} from '~/modules/auth/decorators/permission.decorator'; -import { MenuDto, MenuQueryDto, MenuUpdateDto } from './menu.dto' -import { MenuItemInfo } from './menu.model' -import { MenuService } from './menu.service' +import { MenuDto, MenuQueryDto, MenuUpdateDto } from './menu.dto'; +import { MenuItemInfo } from './menu.model'; +import { MenuService } from './menu.service'; export const permissions = definePermission('system:menu', { LIST: 'list', @@ -26,7 +30,7 @@ export const permissions = definePermission('system:menu', { READ: 'read', UPDATE: 'update', DELETE: 'delete', -} as const) +} as const); @ApiTags('System - 菜单权限模块') @ApiSecurityAuth() @@ -39,14 +43,14 @@ export class MenuController { @ApiResult({ type: [MenuItemInfo] }) @Perm(permissions.LIST) async list(@Query() dto: MenuQueryDto) { - return this.menuService.list(dto) + return this.menuService.list(dto); } @Get(':id') @ApiOperation({ summary: '获取菜单或权限信息' }) @Perm(permissions.READ) async info(@IdParam() id: number) { - return this.menuService.getMenuItemAndParentInfo(id) + return this.menuService.getMenuItemAndParentInfo(id); } @Post() @@ -54,31 +58,28 @@ export class MenuController { @Perm(permissions.CREATE) async create(@Body() dto: MenuDto): Promise { // check - await this.menuService.check(dto) - if (!dto.parentId) - dto.parentId = null + await this.menuService.check(dto); + if (!dto.parentId) dto.parentId = null; - await this.menuService.create(dto) + await this.menuService.create(dto); if (dto.type === 2) { // 如果是权限发生更改,则刷新所有在线用户的权限 - await this.menuService.refreshOnlineUserPerms() + await this.menuService.refreshOnlineUserPerms(); } } @Put(':id') @ApiOperation({ summary: '更新菜单或权限' }) @Perm(permissions.UPDATE) - async update( - @IdParam() id: number, @Body() dto: MenuUpdateDto): Promise { + async update(@IdParam() id: number, @Body() dto: MenuUpdateDto): Promise { // check - await this.menuService.check(dto) - if (dto.parentId === -1 || !dto.parentId) - dto.parentId = null + await this.menuService.check(dto); + if (dto.parentId === -1 || !dto.parentId) dto.parentId = 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(); } } @@ -87,18 +88,18 @@ export class MenuController { @Perm(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(); } @Get('permissions') @ApiOperation({ summary: '获取后端定义的所有权限集' }) async getPermissions(): Promise { - return getDefinePermissions() + return getDefinePermissions(); } } diff --git a/src/modules/system/menu/menu.dto.ts b/src/modules/system/menu/menu.dto.ts index 5a24aab..4d734e5 100644 --- a/src/modules/system/menu/menu.dto.ts +++ b/src/modules/system/menu/menu.dto.ts @@ -1,4 +1,4 @@ -import { ApiProperty, PartialType } from '@nestjs/swagger' +import { ApiProperty, PartialType } from '@nestjs/swagger'; import { IsBoolean, IsIn, @@ -8,79 +8,79 @@ 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() - parentId: number + parentId: 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: false }) @ValidateIf(o => o.type !== 2) @IsBoolean() - isExt: boolean + isExt: boolean; @ApiProperty({ description: '外链打开方式', default: 1 }) @ValidateIf((o: MenuDto) => o.isExt) @IsIn([1, 2]) - extOpenMode: number + extOpenMode: number; @ApiProperty({ description: '菜单是否显示', default: 1 }) @ValidateIf((o: MenuDto) => o.type !== 2) @IsIn([0, 1]) - show: number + show: number; @ApiProperty({ description: '设置当前路由高亮的菜单项,一般用于详情页' }) @ValidateIf((o: MenuDto) => o.type !== 2 && o.show === 0) @IsString() @IsOptional() - activeMenu?: string + activeMenu?: string; @ApiProperty({ description: '是否开启页面缓存', default: 1 }) @ValidateIf((o: MenuDto) => 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: MenuDto) => o.type !== 2) @IsString() - icon?: string + icon?: string; @ApiProperty({ description: '对应权限' }) @ValidateIf((o: MenuDto) => o.type === 2) @IsString() @IsOptional() - permission: string + permission: string; @ApiProperty({ description: '菜单路由路径或外链' }) @ValidateIf((o: MenuDto) => o.type !== 2) @IsString() @IsOptional() - component?: string + component?: string; } export class MenuUpdateDto extends PartialType(MenuDto) {} diff --git a/src/modules/system/menu/menu.entity.ts b/src/modules/system/menu/menu.entity.ts index 950d057..e31a638 100644 --- a/src/modules/system/menu/menu.entity.ts +++ b/src/modules/system/menu/menu.entity.ts @@ -1,55 +1,55 @@ -import { Column, Entity, ManyToMany, Relation } from 'typeorm' +import { Column, Entity, ManyToMany, Relation } from 'typeorm'; -import { CommonEntity } from '~/common/entity/common.entity' +import { CommonEntity } from '~/common/entity/common.entity'; -import { RoleEntity } from '../role/role.entity' +import { RoleEntity } from '../role/role.entity'; @Entity({ name: 'sys_menu' }) export class MenuEntity extends CommonEntity { @Column({ name: 'parent_id', nullable: true }) - parentId: number + parentId: 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({ name: 'is_ext', type: 'boolean', default: false }) - isExt: boolean + isExt: boolean; @Column({ name: 'ext_open_mode', type: 'tinyint', default: 1 }) - extOpenMode: number + extOpenMode: number; @Column({ name: 'keep_alive', type: 'tinyint', default: 1 }) - keepAlive: number + keepAlive: number; @Column({ type: 'tinyint', default: 1 }) - show: number + show: number; @Column({ name: 'active_menu', nullable: true }) - activeMenu: string + activeMenu: string; @Column({ type: 'tinyint', default: 1 }) - status: number + status: number; @ManyToMany(() => RoleEntity, role => role.menus, { onDelete: 'CASCADE', }) - roles: Relation + roles: Relation; } diff --git a/src/modules/system/menu/menu.model.ts b/src/modules/system/menu/menu.model.ts index 36ca084..27a3b4c 100644 --- a/src/modules/system/menu/menu.model.ts +++ b/src/modules/system/menu/menu.model.ts @@ -1,8 +1,8 @@ -import { ApiProperty } from '@nestjs/swagger' +import { ApiProperty } from '@nestjs/swagger'; -import { MenuEntity } from './menu.entity' +import { MenuEntity } from './menu.entity'; export class MenuItemInfo extends MenuEntity { @ApiProperty({ type: [MenuItemInfo] }) - children: MenuItemInfo[] + children: MenuItemInfo[]; } diff --git a/src/modules/system/menu/menu.module.ts b/src/modules/system/menu/menu.module.ts index cdb6ead..bfe18d5 100644 --- a/src/modules/system/menu/menu.module.ts +++ b/src/modules/system/menu/menu.module.ts @@ -1,22 +1,18 @@ -import { Module, forwardRef } from '@nestjs/common' -import { TypeOrmModule } from '@nestjs/typeorm' +import { Module, forwardRef } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; -import { SseService } from '~/modules/sse/sse.service' +import { SseService } from '~/modules/sse/sse.service'; -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, SseService] +const providers = [MenuService, SseService]; @Module({ - imports: [ - TypeOrmModule.forFeature([MenuEntity]), - - forwardRef(() => RoleModule), - ], + imports: [TypeOrmModule.forFeature([MenuEntity]), forwardRef(() => RoleModule)], controllers: [MenuController], providers: [...providers], exports: [TypeOrmModule, ...providers], diff --git a/src/modules/system/menu/menu.service.ts b/src/modules/system/menu/menu.service.ts index 85ce773..9adaf75 100644 --- a/src/modules/system/menu/menu.service.ts +++ b/src/modules/system/menu/menu.service.ts @@ -1,23 +1,23 @@ -import { InjectRedis } from '@liaoliaots/nestjs-redis' -import { Injectable } from '@nestjs/common' -import { InjectRepository } from '@nestjs/typeorm' -import Redis from 'ioredis' -import { concat, isEmpty, isNumber, 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, isNumber, 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 { RedisKeys } from '~/constants/cache.constant' -import { ErrorEnum } from '~/constants/error-code.constant' -import { genAuthPermKey, genAuthTokenKey } from '~/helper/genRedisKey' -import { SseService } from '~/modules/sse/sse.service' -import { MenuEntity } from '~/modules/system/menu/menu.entity' +import { BusinessException } from '~/common/exceptions/biz.exception'; +import { RedisKeys } from '~/constants/cache.constant'; +import { ErrorEnum } from '~/constants/error-code.constant'; +import { genAuthPermKey, genAuthTokenKey } from '~/helper/genRedisKey'; +import { SseService } from '~/modules/sse/sse.service'; +import { MenuEntity } from '~/modules/system/menu/menu.entity'; -import { deleteEmptyChildren, generatorMenu, generatorRouters } from '~/utils' +import { deleteEmptyChildren, generatorMenu, generatorRouters } from '~/utils'; -import { RoleService } from '../role/role.service' +import { RoleService } from '../role/role.service'; -import { MenuDto, MenuQueryDto, MenuUpdateDto } from './menu.dto' +import { MenuDto, MenuQueryDto, MenuUpdateDto } from './menu.dto'; @Injectable() export class MenuService { @@ -26,19 +26,13 @@ export class MenuService { @InjectRepository(MenuEntity) private menuRepository: Repository, private roleService: RoleService, - private sseService: SseService, + private sseService: SseService ) {} /** * 获取所有菜单以及权限 */ - async list({ - name, - path, - permission, - component, - status, - }: MenuQueryDto): Promise { + async list({ name, path, permission, component, status }: MenuQueryDto): Promise { const menus = await this.menuRepository.find({ where: { ...(name && { name: Like(`%${name}%`) }), @@ -48,51 +42,49 @@ export class MenuService { ...(isNumber(status) ? { status } : null), }, 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 { - const result = await this.menuRepository.save(menu) - this.sseService.noticeClientToUpdateMenusByMenuIds([result.id]) + const result = await this.menuRepository.save(menu); + this.sseService.noticeClientToUpdateMenusByMenuIds([result.id]); } async update(id: number, menu: MenuUpdateDto): Promise { - await this.menuRepository.update(id, menu) - this.sseService.noticeClientToUpdateMenusByMenuIds([id]) + await this.menuRepository.update(id, menu); + this.sseService.noticeClientToUpdateMenusByMenuIds([id]); } /** * 根据角色获取所有菜单 */ 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 (isEmpty(roleIds)) - return generatorRouters([]) + if (isEmpty(roleIds)) return generatorRouters([]); if (this.roleService.hasAdminRole(roleIds)) { - menus = await this.menuRepository.find({ order: { orderNo: 'ASC' } }) - } - else { + 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; } /** @@ -101,18 +93,15 @@ export class MenuService { async check(dto: Partial): Promise { if (dto.type === 2 && !dto.parentId) { // 无法直接创建权限,必须有parent - throw new BusinessException(ErrorEnum.PERMISSION_REQUIRES_PARENT) + throw new BusinessException(ErrorEnum.PERMISSION_REQUIRES_PARENT); } if (dto.type === 1 && dto.parentId) { - const parent = await this.getMenuItemInfo(dto.parentId) - if (isEmpty(parent)) - throw new BusinessException(ErrorEnum.PARENT_MENU_NOT_FOUND) + const parent = await this.getMenuItemInfo(dto.parentId); + if (isEmpty(parent)) throw new BusinessException(ErrorEnum.PARENT_MENU_NOT_FOUND); if (parent && parent.type === 1) { // 当前新增为菜单但父节点也为菜单时为非法操作 - throw new BusinessException( - ErrorEnum.ILLEGAL_OPERATION_DIRECTORY_PARENT, - ) + throw new BusinessException(ErrorEnum.ILLEGAL_OPERATION_DIRECTORY_PARENT); } } } @@ -121,8 +110,8 @@ export class MenuService { * 查找当前菜单下的子菜单,目录以及菜单 */ async findChildMenus(mid: number): Promise { - const allMenus: any = [] - const menus = await this.menuRepository.findBy({ parentId: mid }) + const allMenus: any = []; + const menus = await this.menuRepository.findBy({ parentId: mid }); // if (_.isEmpty(menus)) { // return allMenus; // } @@ -130,12 +119,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; } /** @@ -143,46 +132,44 @@ 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.parentId) - parentMenu = await this.menuRepository.findOneBy({ id: menu.parentId }) + parentMenu = await this.menuRepository.findOneBy({ id: menu.parentId }); - 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 + }); + } else { + if (isEmpty(roleIds)) return permission; result = await this.menuRepository .createQueryBuilder('menu') @@ -190,37 +177,36 @@ 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 = uniq(permission) + result.forEach(e => { + if (e.permission) permission = concat(permission, e.permission.split(',')); + }); + 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(genAuthTokenKey(uid)) + const perms = await this.getPermissions(uid); + const online = await this.redis.get(genAuthTokenKey(uid)); if (online) { // 判断是否在线 - await this.redis.set(genAuthPermKey(uid), JSON.stringify(perms)) - console.log('refreshPerms') + await this.redis.set(genAuthPermKey(uid), JSON.stringify(perms)); + console.log('refreshPerms'); - this.sseService.noticeClientToUpdateMenusByUserIds([uid]) + this.sseService.noticeClientToUpdateMenusByUserIds([uid]); } } @@ -228,19 +214,19 @@ export class MenuService { * 刷新所有在线用户的权限 */ async refreshOnlineUserPerms(): Promise { - const onlineUserIds: string[] = await this.redis.keys(genAuthTokenKey('*')) + const onlineUserIds: string[] = await this.redis.keys(genAuthTokenKey('*')); if (onlineUserIds && onlineUserIds.length > 0) { const promiseArr = onlineUserIds .map(i => Number.parseInt(i.split(RedisKeys.AUTH_TOKEN_PREFIX)[1])) .filter(i => i) - .map(async (uid) => { - const perms = await this.getPermissions(uid) - await this.redis.set(genAuthPermKey(uid), JSON.stringify(perms)) - return uid - }) - const uids = await Promise.all(promiseArr) - console.log('refreshOnlineUserPerms') - this.sseService.noticeClientToUpdateMenusByUserIds(uids) + .map(async uid => { + const perms = await this.getPermissions(uid); + await this.redis.set(genAuthPermKey(uid), JSON.stringify(perms)); + return uid; + }); + const uids = await Promise.all(promiseArr); + console.log('refreshOnlineUserPerms'); + this.sseService.noticeClientToUpdateMenusByUserIds(uids); } } @@ -254,6 +240,6 @@ export class MenuService { id, }, }, - })) + })); } } diff --git a/src/modules/system/online/online.controller.ts b/src/modules/system/online/online.controller.ts index c484037..e1069cd 100644 --- a/src/modules/system/online/online.controller.ts +++ b/src/modules/system/online/online.controller.ts @@ -1,23 +1,23 @@ -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 { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator' +import { Perm, definePermission } 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'; export const permissions = definePermission('system:online', { LIST: 'list', KICK: 'kick', -} as const) +} as const); @ApiTags('System - 在线用户模块') @ApiSecurityAuth() @@ -31,16 +31,15 @@ export class OnlineController { @ApiResult({ type: [OnlineUserInfo] }) @Perm(permissions.LIST) async list(@AuthUser() user: IAuthUser): Promise { - return this.onlineService.listOnlineUser(user.uid) + return this.onlineService.listOnlineUser(user.uid); } @Post('kick') @ApiOperation({ summary: '下线指定在线用户' }) @Perm(permissions.KICK) async kick(@Body() dto: KickDto, @AuthUser() user: IAuthUser): Promise { - if (dto.id === user.uid) - throw new BusinessException(ErrorEnum.NOT_ALLOWED_TO_LOGOUT_USER) + if (dto.id === user.uid) 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/src/modules/system/online/online.dto.ts b/src/modules/system/online/online.dto.ts index fbe8f8b..736a2c8 100644 --- a/src/modules/system/online/online.dto.ts +++ b/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/src/modules/system/online/online.model.ts b/src/modules/system/online/online.model.ts index a08f812..9c140d3 100644 --- a/src/modules/system/online/online.model.ts +++ b/src/modules/system/online/online.model.ts @@ -1,27 +1,27 @@ -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: '系统' }) - os: string + os: string; @ApiProperty({ description: '浏览器' }) - browser: string + browser: string; @ApiProperty({ description: '是否禁用' }) - disable: boolean + disable: boolean; } diff --git a/src/modules/system/online/online.module.ts b/src/modules/system/online/online.module.ts index 4de292a..793e71b 100644 --- a/src/modules/system/online/online.module.ts +++ b/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 '~/socket/socket.module' +import { AuthModule } from '~/modules/auth/auth.module'; +import { SocketModule } from '~/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/src/modules/system/online/online.service.ts b/src/modules/system/online/online.service.ts index 31ad4c0..8bf11fa 100644 --- a/src/modules/system/online/online.service.ts +++ b/src/modules/system/online/online.service.ts @@ -1,21 +1,21 @@ -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 { RemoteSocket } from 'socket.io' -import { EntityManager } from 'typeorm' +import { RemoteSocket } from 'socket.io'; +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 { BusinessException } from '~/common/exceptions/biz.exception'; +import { ErrorEnum } from '~/constants/error-code.constant'; -import { BusinessEvents } from '~/socket/business-event.constant' -import { AdminEventsGateway } from '~/socket/events/admin.gateway' +import { BusinessEvents } from '~/socket/business-event.constant'; +import { AdminEventsGateway } from '~/socket/events/admin.gateway'; -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 { @@ -23,55 +23,50 @@ export class OnlineService { @InjectEntityManager() private readonly entityManager: EntityManager, private readonly userService: UserService, private readonly adminEventsGateWay: AdminEventsGateway, - private readonly jwtService: JwtService, + private readonly jwtService: JwtService ) {} /** * 罗列在线用户列表 */ async listOnlineUser(currentUid: number): Promise { - const onlineSockets = await this.getOnlineSockets() - if (!onlineSockets || onlineSockets.length <= 0) - return [] + const onlineSockets = await this.getOnlineSockets(); + if (!onlineSockets || onlineSockets.length <= 0) 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 onlineIds = onlineSockets.map(socket => { + 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) - if (uid === rootUserId) - throw new BusinessException(ErrorEnum.NOT_ALLOWED_TO_LOGOUT_USER) + 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); // reset redis keys - await this.userService.forbidden(uid) + await this.userService.forbidden(uid); // socket emit - const socket = await this.findSocketIdByUid(uid) + const socket = await this.findSocketIdByUid(uid); if (socket) { // socket emit event this.adminEventsGateWay.server .to(socket.id) - .emit(BusinessEvents.USER_KICK, { operater: currentUserInfo.username }) + .emit(BusinessEvents.USER_KICK, { operater: currentUserInfo.username }); // close socket - socket.disconnect() + socket.disconnect(); } } /** * 根据用户id列表查找最近登录信息和用户信息 */ - async findLastLoginInfoList( - ids: number[], - currentUid: number, - ): Promise { - const rootUserId = await this.userService.findRootUserId() + async findLastLoginInfoList(ids: number[], currentUid: number): Promise { + 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 @@ -80,12 +75,12 @@ export class OnlineService { WHERE sys_login_log.created_at IN (SELECT MAX(created_at) as createdAt FROM sys_login_log GROUP BY user_id) AND sys_user.id IN (?) `, - [ids], - ) + [ids] + ); if (result) { - const parser = new UAParser() - return result.map((e) => { - const u = parser.setUA(e.ua).getResult() + const parser = new UAParser(); + return result.map(e => { + const u = parser.setUA(e.ua).getResult(); return { id: e.id, ip: e.ip, @@ -96,39 +91,37 @@ 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 []; } /** * 根据uid查找socketid */ async findSocketIdByUid(uid: number): Promise> { - 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 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; } - async filterSocketIdByUidArr( - uids: number[], - ): Promise[]> { - 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 + async filterSocketIdByUidArr(uids: number[]): Promise[]> { + 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; } async getOnlineSockets() { - const onlineSockets = await this.adminEventsGateWay.server.fetchSockets() - return onlineSockets + const onlineSockets = await this.adminEventsGateWay.server.fetchSockets(); + return onlineSockets; } } diff --git a/src/modules/system/param-config/param-config.controller.ts b/src/modules/system/param-config/param-config.controller.ts index b96ab9d..b792eb3 100644 --- a/src/modules/system/param-config/param-config.controller.ts +++ b/src/modules/system/param-config/param-config.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 { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator' -import { ParamConfigEntity } from '~/modules/system/param-config/param-config.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 { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator'; +import { ParamConfigEntity } from '~/modules/system/param-config/param-config.entity'; -import { ParamConfigDto, ParamConfigQueryDto } from './param-config.dto' -import { ParamConfigService } from './param-config.service' +import { ParamConfigDto, ParamConfigQueryDto } from './param-config.dto'; +import { ParamConfigService } from './param-config.service'; export const permissions = definePermission('system:param-config', { LIST: 'list', @@ -17,7 +17,7 @@ export const permissions = definePermission('system:param-config', { READ: 'read', UPDATE: 'update', DELETE: 'delete', -} as const) +} as const); @ApiTags('System - 参数配置模块') @ApiSecurityAuth() @@ -30,15 +30,15 @@ export class ParamConfigController { @ApiResult({ type: [ParamConfigEntity], isPage: true }) @Perm(permissions.LIST) async list(@Query() dto: ParamConfigQueryDto): Promise> { - return this.paramConfigService.page(dto) + return this.paramConfigService.page(dto); } @Post() @ApiOperation({ summary: '新增参数配置' }) @Perm(permissions.CREATE) async create(@Body() dto: ParamConfigDto): Promise { - await this.paramConfigService.isExistKey(dto.key) - await this.paramConfigService.create(dto) + await this.paramConfigService.isExistKey(dto.key); + await this.paramConfigService.create(dto); } @Get(':id') @@ -46,20 +46,20 @@ export class ParamConfigController { @ApiResult({ type: ParamConfigEntity }) @Perm(permissions.READ) async info(@IdParam() id: number): Promise { - return this.paramConfigService.findOne(id) + return this.paramConfigService.findOne(id); } @Post(':id') @ApiOperation({ summary: '更新参数配置' }) @Perm(permissions.UPDATE) async update(@IdParam() id: number, @Body() dto: ParamConfigDto): Promise { - await this.paramConfigService.update(id, dto) + await this.paramConfigService.update(id, dto); } @Delete(':id') @ApiOperation({ summary: '删除指定的参数配置' }) @Perm(permissions.DELETE) async delete(@IdParam() id: number): Promise { - await this.paramConfigService.delete(id) + await this.paramConfigService.delete(id); } } diff --git a/src/modules/system/param-config/param-config.dto.ts b/src/modules/system/param-config/param-config.dto.ts index 5b720ba..921d5ed 100644 --- a/src/modules/system/param-config/param-config.dto.ts +++ b/src/modules/system/param-config/param-config.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 { PagerDto } from '~/common/dto/pager.dto' +import { PagerDto } from '~/common/dto/pager.dto'; export class ParamConfigDto { @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 ParamConfigQueryDto extends PagerDto { @ApiProperty({ description: '参数名称' }) @IsString() @IsOptional() - name: string + name: string; } diff --git a/src/modules/system/param-config/param-config.entity.ts b/src/modules/system/param-config/param-config.entity.ts index 2b5c4e0..47153e7 100644 --- a/src/modules/system/param-config/param-config.entity.ts +++ b/src/modules/system/param-config/param-config.entity.ts @@ -1,23 +1,23 @@ -import { ApiProperty } from '@nestjs/swagger' -import { Column, Entity } from 'typeorm' +import { ApiProperty } from '@nestjs/swagger'; +import { Column, Entity } from 'typeorm'; -import { CommonEntity } from '~/common/entity/common.entity' +import { CommonEntity } from '~/common/entity/common.entity'; @Entity({ name: 'sys_config' }) export class ParamConfigEntity extends CommonEntity { @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/src/modules/system/param-config/param-config.module.ts b/src/modules/system/param-config/param-config.module.ts index e1c7a9e..0da5acf 100644 --- a/src/modules/system/param-config/param-config.module.ts +++ b/src/modules/system/param-config/param-config.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 { ParamConfigController } from './param-config.controller' -import { ParamConfigEntity } from './param-config.entity' -import { ParamConfigService } from './param-config.service' +import { ParamConfigController } from './param-config.controller'; +import { ParamConfigEntity } from './param-config.entity'; +import { ParamConfigService } from './param-config.service'; -const services = [ParamConfigService] +const services = [ParamConfigService]; @Module({ imports: [TypeOrmModule.forFeature([ParamConfigEntity])], diff --git a/src/modules/system/param-config/param-config.service.ts b/src/modules/system/param-config/param-config.service.ts index 36cbee3..926b5ae 100644 --- a/src/modules/system/param-config/param-config.service.ts +++ b/src/modules/system/param-config/param-config.service.ts @@ -1,21 +1,21 @@ -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 { ParamConfigEntity } from '~/modules/system/param-config/param-config.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 { ParamConfigEntity } from '~/modules/system/param-config/param-config.entity'; -import { ParamConfigDto, ParamConfigQueryDto } from './param-config.dto' +import { ParamConfigDto, ParamConfigQueryDto } from './param-config.dto'; @Injectable() export class ParamConfigService { constructor( @InjectRepository(ParamConfigEntity) - private paramConfigRepository: Repository, + private paramConfigRepository: Repository ) {} /** @@ -26,66 +26,64 @@ export class ParamConfigService { pageSize, name, }: ParamConfigQueryDto): Promise> { - const queryBuilder = this.paramConfigRepository.createQueryBuilder('config') + const queryBuilder = this.paramConfigRepository.createQueryBuilder('config'); if (name) { queryBuilder.where('config.name LIKE :name', { name: `%${name}%`, - }) + }); } - return paginate(queryBuilder, { page, pageSize }) + return paginate(queryBuilder, { page, pageSize }); } /** * 获取参数总数 */ async countConfigList(): Promise { - return this.paramConfigRepository.count() + return this.paramConfigRepository.count(); } /** * 新增 */ async create(dto: ParamConfigDto): Promise { - await this.paramConfigRepository.insert(dto) + await this.paramConfigRepository.insert(dto); } /** * 更新 */ async update(id: number, dto: Partial): Promise { - await this.paramConfigRepository.update(id, dto) + await this.paramConfigRepository.update(id, dto); } /** * 删除 */ async delete(id: number): Promise { - await this.paramConfigRepository.delete(id) + await this.paramConfigRepository.delete(id); } /** * 查询单个 */ async findOne(id: number): Promise { - return this.paramConfigRepository.findOneBy({ id }) + return this.paramConfigRepository.findOneBy({ id }); } async isExistKey(key: string): Promise { - const result = await this.paramConfigRepository.findOneBy({ key }) - if (result) - throw new BusinessException(ErrorEnum.PARAMETER_CONFIG_KEY_EXISTS) + const result = await this.paramConfigRepository.findOneBy({ key }); + if (result) throw new BusinessException(ErrorEnum.PARAMETER_CONFIG_KEY_EXISTS); } async findValueByKey(key: string): Promise { const result = await this.paramConfigRepository.findOne({ where: { key }, select: ['value'], - }) - if (result) - return result.value + }); + if (result) return result.value; - return null + return null; } } diff --git a/src/modules/system/role/role.controller.ts b/src/modules/system/role/role.controller.ts index 1f5b7a0..05ba9c6 100644 --- a/src/modules/system/role/role.controller.ts +++ b/src/modules/system/role/role.controller.ts @@ -7,21 +7,21 @@ 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 { PagerDto } from '~/common/dto/pager.dto' -import { Perm, definePermission } 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 { PagerDto } from '~/common/dto/pager.dto'; +import { Perm, definePermission } 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, RoleQueryDto, RoleUpdateDto } from './role.dto' -import { RoleInfo } from './role.model' -import { RoleService } from './role.service' +import { RoleDto, RoleQueryDto, RoleUpdateDto } from './role.dto'; +import { RoleInfo } from './role.model'; +import { RoleService } from './role.service'; export const permissions = definePermission('system:role', { LIST: 'list', @@ -29,7 +29,7 @@ export const permissions = definePermission('system:role', { READ: 'read', UPDATE: 'update', DELETE: 'delete', -} as const) +} as const); @ApiTags('System - 角色模块') @ApiSecurityAuth() @@ -37,7 +37,7 @@ export const permissions = definePermission('system:role', { export class RoleController { constructor( private roleService: RoleService, - private menuService: MenuService, + private menuService: MenuService ) {} @Get() @@ -45,7 +45,7 @@ export class RoleController { @ApiResult({ type: [RoleEntity], isPage: true }) @Perm(permissions.LIST) async list(@Query() dto: RoleQueryDto) { - return this.roleService.findAll(dto) + return this.roleService.findAll(dto); } @Get(':id') @@ -53,23 +53,22 @@ export class RoleController { @ApiResult({ type: RoleInfo }) @Perm(permissions.READ) async info(@IdParam() id: number) { - return this.roleService.info(id) + return this.roleService.info(id); } @Post() @ApiOperation({ summary: '新增角色' }) @Perm(permissions.CREATE) async create(@Body() dto: RoleDto): Promise { - await this.roleService.create(dto) + await this.roleService.create(dto); } @Put(':id') @ApiOperation({ summary: '更新角色' }) @Perm(permissions.UPDATE) - async update( - @IdParam() id: number, @Body() dto: RoleUpdateDto): Promise { - await this.roleService.update(id, dto) - await this.menuService.refreshOnlineUserPerms() + async update(@IdParam() id: number, @Body() dto: RoleUpdateDto): Promise { + await this.roleService.update(id, dto); + await this.menuService.refreshOnlineUserPerms(); } @Delete(':id') @@ -77,9 +76,9 @@ export class RoleController { @Perm(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/src/modules/system/role/role.dto.ts b/src/modules/system/role/role.dto.ts index 2e9d0e2..7c48dcd 100644 --- a/src/modules/system/role/role.dto.ts +++ b/src/modules/system/role/role.dto.ts @@ -1,48 +1,38 @@ -import { ApiProperty, IntersectionType, PartialType } from '@nestjs/swagger' -import { - IsArray, - IsIn, - IsInt, - IsOptional, - IsString, - Matches, - MinLength, -} from 'class-validator' -import { PagerDto } from '~/common/dto/pager.dto' +import { ApiProperty, IntersectionType, PartialType } from '@nestjs/swagger'; +import { IsArray, IsIn, IsInt, IsOptional, IsString, Matches, MinLength } from 'class-validator'; +import { PagerDto } from '~/common/dto/pager.dto'; 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[]; } export class RoleUpdateDto extends PartialType(RoleDto) {} export class RoleQueryDto extends IntersectionType(PagerDto, PartialType(RoleDto)) { - @ApiProperty({ description: '状态', example: 0, required: false }) @IsInt() @IsOptional() - status?: number - + status?: number; } diff --git a/src/modules/system/role/role.entity.ts b/src/modules/system/role/role.entity.ts index 1317697..7a4854b 100644 --- a/src/modules/system/role/role.entity.ts +++ b/src/modules/system/role/role.entity.ts @@ -1,36 +1,36 @@ -import { ApiHideProperty, ApiProperty } from '@nestjs/swagger' -import { Column, Entity, JoinTable, ManyToMany, Relation } from 'typeorm' +import { ApiHideProperty, ApiProperty } from '@nestjs/swagger'; +import { Column, Entity, JoinTable, ManyToMany, Relation } from 'typeorm'; -import { CommonEntity } from '~/common/entity/common.entity' +import { CommonEntity } from '~/common/entity/common.entity'; -import { UserEntity } from '../../user/user.entity' -import { MenuEntity } from '../menu/menu.entity' +import { UserEntity } from '../../user/user.entity'; +import { MenuEntity } from '../menu/menu.entity'; @Entity({ name: 'sys_role' }) export class RoleEntity extends CommonEntity { @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; @Column({ nullable: true }) @ApiProperty({ description: '是否默认用户' }) - default: boolean + default: boolean; @ApiHideProperty() @ManyToMany(() => UserEntity, user => user.roles) - users: Relation + users: Relation; @ApiHideProperty() @ManyToMany(() => MenuEntity, menu => menu.roles, {}) @@ -39,5 +39,5 @@ export class RoleEntity extends CommonEntity { joinColumn: { name: 'role_id', referencedColumnName: 'id' }, inverseJoinColumn: { name: 'menu_id', referencedColumnName: 'id' }, }) - menus: Relation + menus: Relation; } diff --git a/src/modules/system/role/role.model.ts b/src/modules/system/role/role.model.ts index 2d11ab4..285df41 100644 --- a/src/modules/system/role/role.model.ts +++ b/src/modules/system/role/role.model.ts @@ -1,8 +1,8 @@ -import { ApiProperty } from '@nestjs/swagger' +import { ApiProperty } from '@nestjs/swagger'; -import { RoleEntity } from './role.entity' +import { RoleEntity } from './role.entity'; export class RoleInfo extends RoleEntity { @ApiProperty({ type: [Number] }) - menuIds: number[] + menuIds: number[]; } diff --git a/src/modules/system/role/role.module.ts b/src/modules/system/role/role.module.ts index 98c24e6..b43e4a2 100644 --- a/src/modules/system/role/role.module.ts +++ b/src/modules/system/role/role.module.ts @@ -1,21 +1,18 @@ -import { Module, forwardRef } from '@nestjs/common' -import { TypeOrmModule } from '@nestjs/typeorm' +import { Module, forwardRef } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; -import { SseService } from '~/modules/sse/sse.service' +import { SseService } from '~/modules/sse/sse.service'; -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, SseService] +const providers = [RoleService, SseService]; @Module({ - imports: [ - TypeOrmModule.forFeature([RoleEntity]), - forwardRef(() => MenuModule), - ], + imports: [TypeOrmModule.forFeature([RoleEntity]), forwardRef(() => MenuModule)], controllers: [RoleController], providers: [...providers], exports: [TypeOrmModule, ...providers], diff --git a/src/modules/system/role/role.service.ts b/src/modules/system/role/role.service.ts index 2f0c807..0908d4f 100644 --- a/src/modules/system/role/role.service.ts +++ b/src/modules/system/role/role.service.ts @@ -1,17 +1,17 @@ -import { Injectable } from '@nestjs/common' -import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm' -import { isEmpty, isNumber } from 'lodash' -import { EntityManager, In, Like, Repository } from 'typeorm' +import { Injectable } from '@nestjs/common'; +import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm'; +import { isEmpty, isNumber } from 'lodash'; +import { EntityManager, In, Like, Repository } from 'typeorm'; -import { PagerDto } from '~/common/dto/pager.dto' -import { ROOT_ROLE_ID } from '~/constants/system.constant' -import { paginate } from '~/helper/paginate' -import { Pagination } from '~/helper/paginate/pagination' -import { SseService } from '~/modules/sse/sse.service' -import { MenuEntity } from '~/modules/system/menu/menu.entity' -import { RoleEntity } from '~/modules/system/role/role.entity' +import { PagerDto } from '~/common/dto/pager.dto'; +import { ROOT_ROLE_ID } from '~/constants/system.constant'; +import { paginate } from '~/helper/paginate'; +import { Pagination } from '~/helper/paginate/pagination'; +import { SseService } from '~/modules/sse/sse.service'; +import { MenuEntity } from '~/modules/system/menu/menu.entity'; +import { RoleEntity } from '~/modules/system/role/role.entity'; -import { RoleDto, RoleQueryDto, RoleUpdateDto } from './role.dto' +import { RoleDto, RoleQueryDto, RoleUpdateDto } from './role.dto'; @Injectable() export class RoleService { @@ -21,8 +21,7 @@ export class RoleService { @InjectRepository(MenuEntity) private menuRepository: Repository, @InjectEntityManager() private entityManager: EntityManager, - private sseService: SseService, - + private sseService: SseService ) {} /** @@ -35,18 +34,16 @@ export class RoleService { value, status, }: RoleQueryDto): Promise> { - const queryBuilder = this.roleRepository - .createQueryBuilder('role') - .where({ + const queryBuilder = this.roleRepository.createQueryBuilder('role').where({ ...(name ? { name: Like(`%${name}%`) } : null), ...(value ? { value: Like(`%${value}%`) } : null), ...(isNumber(status) ? { status } : null), - }) + }); - return paginate(queryBuilder, { - page, - pageSize, - }) + return paginate(queryBuilder, { + page, + pageSize, + }); } /** @@ -58,20 +55,19 @@ export class RoleService { .where({ id, }) - .getOne() + .getOne(); 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 === ROOT_ROLE_ID) - throw new Error('不能删除超级管理员') - await this.roleRepository.delete(id) + if (id === ROOT_ROLE_ID) throw new Error('不能删除超级管理员'); + await this.roleRepository.delete(id); } /** @@ -80,31 +76,29 @@ export class RoleService { async create({ menuIds, ...data }: RoleDto): Promise<{ roleId: number }> { const role = await this.roleRepository.save({ ...data, - menus: menuIds - ? await this.menuRepository.findBy({ id: In(menuIds) }) - : [], - }) + menus: menuIds ? await this.menuRepository.findBy({ id: In(menuIds) }) : [], + }); - return { roleId: role.id } + return { roleId: role.id }; } /** * 更新角色信息 */ async update(id, { menuIds, ...data }: RoleUpdateDto): Promise { - await this.roleRepository.update(id, data) + await this.roleRepository.update(id, data); if (!isEmpty(menuIds)) { // using transaction - await this.entityManager.transaction(async (manager) => { + 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); + }); } } @@ -116,12 +110,11 @@ export class RoleService { where: { users: { id }, }, - }) + }); - if (!isEmpty(roles)) - return roles.map(r => r.id) + if (!isEmpty(roles)) return roles.map(r => r.id); - return [] + return []; } async getRoleValues(ids: number[]): Promise { @@ -129,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 { @@ -137,18 +130,16 @@ export class RoleService { where: { users: { id: uid }, }, - }) + }); if (!isEmpty(roles)) { - return roles.some( - r => r.id === ROOT_ROLE_ID, - ) + return roles.some(r => r.id === ROOT_ROLE_ID); } - return false + return false; } hasAdminRole(rids: number[]): boolean { - return rids.includes(ROOT_ROLE_ID) + return rids.includes(ROOT_ROLE_ID); } /** @@ -161,6 +152,6 @@ export class RoleService { roles: { id }, }, }, - }) + }); } } diff --git a/src/modules/system/serve/serve.controller.ts b/src/modules/system/serve/serve.controller.ts index eb27465..d931f4d 100644 --- a/src/modules/system/serve/serve.controller.ts +++ b/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/src/modules/system/serve/serve.model.ts b/src/modules/system/serve/serve.model.ts index 4c602cb..1fba434 100644 --- a/src/modules/system/serve/serve.model.ts +++ b/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: '制造商' }) - manufacturer?: string + manufacturer?: string; @ApiProperty({ description: '品牌' }) - brand?: string + brand?: string; @ApiProperty({ description: '物理核心数' }) - physicalCores?: number + physicalCores?: number; @ApiProperty({ description: '型号' }) - model?: string + model?: string; @ApiProperty({ description: '速度 in GHz' }) - 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/src/modules/system/serve/serve.module.ts b/src/modules/system/serve/serve.module.ts index db44177..a2963cc 100644 --- a/src/modules/system/serve/serve.module.ts +++ b/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/src/modules/system/serve/serve.service.ts b/src/modules/system/serve/serve.service.ts index 2b92f3a..b20c2f5 100644 --- a/src/modules/system/serve/serve.service.ts +++ b/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 - diskListInfo.forEach((d) => { - diskinfo.size += d.size - diskinfo.available += d.available - diskinfo.used += d.used - }) + 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; + }); return { runtime: { @@ -46,11 +46,11 @@ export class ServeService { speed: cpuinfo.speed, rawCurrentLoad: currentLoadinfo.rawCurrentLoad, rawCurrentLoadIdle: currentLoadinfo.rawCurrentLoadIdle, - coresLoad: currentLoadinfo.cpus.map((e) => { + coresLoad: currentLoadinfo.cpus.map(e => { 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/src/modules/system/system.module.ts b/src/modules/system/system.module.ts index ae4b12f..c919c1c 100644 --- a/src/modules/system/system.module.ts +++ b/src/modules/system/system.module.ts @@ -1,19 +1,19 @@ -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 { DictItemModule } from './dict-item/dict-item.module' -import { DictTypeModule } from './dict-type/dict-type.module' -import { LogModule } from './log/log.module' -import { MenuModule } from './menu/menu.module' -import { OnlineModule } from './online/online.module' -import { ParamConfigModule } from './param-config/param-config.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 { DictItemModule } from './dict-item/dict-item.module'; +import { DictTypeModule } from './dict-type/dict-type.module'; +import { LogModule } from './log/log.module'; +import { MenuModule } from './menu/menu.module'; +import { OnlineModule } from './online/online.module'; +import { ParamConfigModule } from './param-config/param-config.module'; +import { RoleModule } from './role/role.module'; +import { ServeModule } from './serve/serve.module'; +import { TaskModule } from './task/task.module'; const modules = [ UserModule, @@ -27,7 +27,7 @@ const modules = [ TaskModule, OnlineModule, ServeModule, -] +]; @Module({ imports: [ diff --git a/src/modules/system/task/constant.ts b/src/modules/system/task/constant.ts index 53f5d9b..e32bf7a 100644 --- a/src/modules/system/task/constant.ts +++ b/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/src/modules/system/task/task.controller.ts b/src/modules/system/task/task.controller.ts index 6888664..975c0b5 100644 --- a/src/modules/system/task/task.controller.ts +++ b/src/modules/system/task/task.controller.ts @@ -1,15 +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 { Perm, definePermission } 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 { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator'; +import { TaskEntity } from '~/modules/system/task/task.entity'; -import { TaskDto, TaskQueryDto, TaskUpdateDto } from './task.dto' -import { TaskService } from './task.service' +import { TaskDto, TaskQueryDto, TaskUpdateDto } from './task.dto'; +import { TaskService } from './task.service'; export const permissions = definePermission('system:task', { LIST: 'list', @@ -21,7 +21,7 @@ export const permissions = definePermission('system:task', { ONCE: 'once', START: 'start', STOP: 'stop', -} as const) +} as const); @ApiTags('System - 任务调度模块') @ApiSecurityAuth() @@ -34,25 +34,25 @@ export class TaskController { @ApiResult({ type: [TaskEntity], isPage: true }) @Perm(permissions.LIST) async list(@Query() dto: TaskQueryDto): Promise> { - return this.taskService.list(dto) + return this.taskService.list(dto); } @Post() @ApiOperation({ summary: '添加任务' }) @Perm(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') @ApiOperation({ summary: '更新任务' }) @Perm(permissions.UPDATE) async update(@IdParam() id: number, @Body() dto: TaskUpdateDto): 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') @@ -60,39 +60,39 @@ export class TaskController { @ApiResult({ type: TaskEntity }) @Perm(permissions.READ) async info(@IdParam() id: number): Promise { - return this.taskService.info(id) + return this.taskService.info(id); } @Delete(':id') @ApiOperation({ summary: '删除任务' }) @Perm(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: '手动执行一次任务' }) @Perm(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: '停止任务' }) @Perm(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: '启动任务' }) @Perm(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/src/modules/system/task/task.dto.ts b/src/modules/system/task/task.dto.ts index c02d24f..0d9af8b 100644 --- a/src/modules/system/task/task.dto.ts +++ b/src/modules/system/task/task.dto.ts @@ -1,5 +1,5 @@ -import { BadRequestException } from '@nestjs/common' -import { ApiProperty, ApiPropertyOptional, IntersectionType, PartialType } from '@nestjs/swagger' +import { BadRequestException } from '@nestjs/common'; +import { ApiProperty, ApiPropertyOptional, IntersectionType, PartialType } from '@nestjs/swagger'; import { IsDateString, IsIn, @@ -14,30 +14,28 @@ 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 { PagerDto } from '~/common/dto/pager.dto' +import { PagerDto } from '~/common/dto/pager.dto'; // cron 表达式验证,bull lib下引用了cron-parser @ValidatorConstraint({ name: 'isCronExpression', async: false }) export class IsCronExpression implements ValidatorConstraintInterface { validate(value: string, _args: ValidationArguments) { try { - if (isEmpty(value)) - throw new BadRequestException('cron expression is empty') + if (isEmpty(value)) throw new BadRequestException('cron expression is empty'); - parser.parseExpression(value) - return true - } - catch (e) { - return false + parser.parseExpression(value); + return true; + } catch (e) { + return false; } } defaultMessage(_args: ValidationArguments) { - return 'this cron expression ($value) invalid' + return 'this cron expression ($value) invalid'; } } @@ -46,58 +44,58 @@ 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 TaskUpdateDto extends PartialType(TaskDto) {} diff --git a/src/modules/system/task/task.entity.ts b/src/modules/system/task/task.entity.ts index 9ceb548..29738f6 100644 --- a/src/modules/system/task/task.entity.ts +++ b/src/modules/system/task/task.entity.ts @@ -1,55 +1,55 @@ -import { ApiProperty } from '@nestjs/swagger' -import { Column, Entity } from 'typeorm' +import { ApiProperty } from '@nestjs/swagger'; +import { Column, Entity } from 'typeorm'; -import { CommonEntity } from '~/common/entity/common.entity' +import { CommonEntity } from '~/common/entity/common.entity'; @Entity({ name: 'sys_task' }) export class TaskEntity extends CommonEntity { @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/src/modules/system/task/task.module.ts b/src/modules/system/task/task.module.ts index 1fed543..99ce40b 100644 --- a/src/modules/system/task/task.module.ts +++ b/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 { ConfigKeyPaths, IRedisConfig } from '~/config' +import { ConfigKeyPaths, 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/src/modules/system/task/task.processor.ts b/src/modules/system/task/task.processor.ts index d45e24c..57147ab 100644 --- a/src/modules/system/task/task.processor.ts +++ b/src/modules/system/task/task.processor.ts @@ -1,44 +1,43 @@ -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) export class TaskConsumer { constructor( private taskService: TaskService, - private taskLogService: TaskLogService, + private taskLogService: TaskLogService ) {} @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) - } - catch (e) { - const timing = Date.now() - startTime + await this.taskLogService.create(data.id, 1, timing); + } catch (e) { + 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/src/modules/system/task/task.service.ts b/src/modules/system/task/task.service.ts index eaba96d..dddf2ed 100644 --- a/src/modules/system/task/task.service.ts +++ b/src/modules/system/task/task.service.ts @@ -1,39 +1,35 @@ -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, isNumber } 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, isNumber } 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' -import { TaskDto, TaskQueryDto, TaskUpdateDto } from './task.dto' +import { SYS_TASK_QUEUE_NAME, SYS_TASK_QUEUE_PREFIX, TaskStatus } from './constant'; +import { TaskDto, TaskQueryDto, TaskUpdateDto } from './task.dto'; @Injectable() export class TaskService implements OnModuleInit { - private logger = new Logger(TaskService.name) + private logger = new Logger(TaskService.name); constructor( @InjectRepository(TaskEntity) @@ -41,31 +37,31 @@ export class TaskService implements OnModuleInit { @InjectQueue(SYS_TASK_QUEUE_NAME) private taskQueue: Queue, private moduleRef: ModuleRef, private reflector: Reflector, - @InjectRedis() private redis: Redis, + @InjectRedis() private redis: Redis ) {} /** * 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', @@ -74,19 +70,18 @@ export class TaskService implements OnModuleInit { 'paused', 'waiting', 'completed', - ]) - jobs.forEach((j) => { - j.remove() - }) + ]); + jobs.forEach(j => { + 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) + for (const t of tasks) await this.start(t); } // 启动后释放锁 - await this.redis.del(initKey) + await this.redis.del(initKey); } async list({ @@ -105,35 +100,30 @@ export class TaskService implements OnModuleInit { ...(type ? { type } : null), ...(isNumber(status) ? { status } : null), }) - .orderBy('task.id', 'ASC') + .orderBy('task.id', 'ASC'); - return paginate(queryBuilder, { page, pageSize }) + return paginate(queryBuilder, { page, pageSize }); } /** * task info */ async info(id: number): Promise { - const task = this.taskRepository - .createQueryBuilder('task') - .where({ id }) - .getOne() + const task = this.taskRepository.createQueryBuilder('task').where({ id }).getOne(); - if (!task) - throw new NotFoundException('Task Not Found') + if (!task) throw new NotFoundException('Task Not Found'); - return task + return task; } /** * delete task */ async delete(task: TaskEntity): Promise { - if (!task) - throw new BadRequestException('Task is Empty') + if (!task) 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); } /** @@ -143,80 +133,69 @@ export class TaskService implements OnModuleInit { if (task) { 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') + { jobId: task.id, removeOnComplete: true, removeOnFail: true } + ); + } else { + 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) - if (result.status === 0) - await this.stop(task) - else if (result.status === TaskStatus.Activited) - await this.start(task) + const result = await this.taskRepository.save(dto); + const task = await this.info(result.id); + if (result.status === 0) await this.stop(task); + else if (result.status === TaskStatus.Activited) await this.start(task); } async update(id: number, dto: TaskUpdateDto): Promise { - await this.taskRepository.update(id, dto) - const task = await this.info(id) - if (task.status === 0) - await this.stop(task) - else if (task.status === TaskStatus.Activited) - await this.start(task) + await this.taskRepository.update(id, dto); + const task = await this.info(id); + if (task.status === 0) await this.stop(task); + else if (task.status === TaskStatus.Activited) await this.start(task); } /** * 启动任务 */ async start(task: TaskEntity): Promise { - if (!task) - throw new BadRequestException('Task is Empty') + if (!task) 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 { + }; + } 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 + if (task.startTime) repeat.startDate = task.startTime; - if (task.endTime) - repeat.endDate = task.endTime + if (task.endTime) repeat.endDate = task.endTime; } - if (task.limit > 0) - repeat.limit = task.limit + if (task.limit > 0) 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 }, - ) + { 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 { + }); + } 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'); } } @@ -224,15 +203,14 @@ export class TaskService implements OnModuleInit { * 停止任务 */ async stop(task: TaskEntity): Promise { - if (!task) - throw new BadRequestException('Task is Empty') + if (!task) 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', @@ -241,14 +219,14 @@ export class TaskService implements OnModuleInit { 'paused', 'waiting', 'completed', - ]) + ]); jobs .filter(j => j.data.id === task.id) - .forEach(async (j) => { - await j.remove() - }) + .forEach(async j => { + 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 @@ -261,26 +239,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 ids = jobs.map((e) => { - return e.id - }) - return ids.includes(jobId) + const jobs = await this.taskQueue.getRepeatableJobs(); + const ids = jobs.map(e => { + 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; } } } @@ -289,38 +267,27 @@ export class TaskService implements OnModuleInit { * 检测service是否有注解定义 * @param serviceName service */ - async checkHasMissionMeta( - nameOrInstance: string | unknown, - exec: string, - ): Promise { + async checkHasMissionMeta(nameOrInstance: string | unknown, exec: string): Promise { try { - let service: any + let service: any; if (typeof nameOrInstance === 'string') - service = await this.moduleRef.get(nameOrInstance, { strict: false }) - else - service = nameOrInstance + service = await this.moduleRef.get(nameOrInstance, { strict: false }); + else service = nameOrInstance; // 所执行的任务不存在 - if (!service || !(exec in service)) - throw new NotFoundException('任务不存在') + if (!service || !(exec in service)) throw new NotFoundException('任务不存在'); // 检测是否有Mission注解 - const hasMission = this.reflector.get( - MISSION_DECORATOR_KEY, - service.constructor, - ) + const hasMission = this.reflector.get(MISSION_DECORATOR_KEY, service.constructor); // 如果没有,则抛出错误 - if (!hasMission) - throw new BusinessException(ErrorEnum.INSECURE_MISSION) - } - catch (e) { + if (!hasMission) throw new BusinessException(ErrorEnum.INSECURE_MISSION); + } catch (e) { if (e instanceof UnknownElementException) { // 任务不存在 - throw new NotFoundException('任务不存在') - } - else { + throw new NotFoundException('任务不存在'); + } else { // 其余错误则不处理,继续抛出 - throw e + throw e; } } } @@ -330,29 +297,26 @@ export class TaskService implements OnModuleInit { */ async callService(name: string, args: string): Promise { if (name) { - const [serviceName, methodName] = name.split('.') - if (!methodName) - throw new BadRequestException('serviceName define BadRequestException') + const [serviceName, methodName] = name.split('.'); + if (!methodName) 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]() - } - else { + await service[methodName](); + } else { // 参数安全判断 - const parseArgs = this.safeParse(args) + const parseArgs = this.safeParse(args); if (Array.isArray(parseArgs)) { // 数组形式则自动扩展成方法参数回掉 - await service[methodName](...parseArgs) - } - else { - await service[methodName](parseArgs) + await service[methodName](...parseArgs); + } else { + await service[methodName](parseArgs); } } } @@ -360,10 +324,9 @@ export class TaskService implements OnModuleInit { safeParse(args: string): unknown | string { try { - return JSON.parse(args) - } - catch (e) { - return args + return JSON.parse(args); + } catch (e) { + return args; } } } diff --git a/src/modules/system/task/task.ts b/src/modules/system/task/task.ts index 26d1e2c..10c09b3 100644 --- a/src/modules/system/task/task.ts +++ b/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/src/modules/tasks/jobs/email.job.ts b/src/modules/tasks/jobs/email.job.ts index 1f923e4..be1bc61 100644 --- a/src/modules/tasks/jobs/email.job.ts +++ b/src/modules/tasks/jobs/email.job.ts @@ -1,9 +1,9 @@ -import { BadRequestException, Injectable } from '@nestjs/common' +import { BadRequestException, Injectable } from '@nestjs/common'; -import { LoggerService } from '~/shared/logger/logger.service' -import { MailerService } from '~/shared/mailer/mailer.service' +import { LoggerService } from '~/shared/logger/logger.service'; +import { MailerService } from '~/shared/mailer/mailer.service'; -import { Mission } from '../mission.decorator' +import { Mission } from '../mission.decorator'; /** * Api接口请求类型任务 @@ -13,17 +13,16 @@ import { Mission } from '../mission.decorator' export class EmailJob { constructor( private readonly emailService: MailerService, - private readonly logger: LoggerService, + private readonly logger: LoggerService ) {} 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) - } - else { - throw new BadRequestException('Email send job param is empty') + 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'); } } } diff --git a/src/modules/tasks/jobs/http-request.job.ts b/src/modules/tasks/jobs/http-request.job.ts index 359cb06..7922913 100644 --- a/src/modules/tasks/jobs/http-request.job.ts +++ b/src/modules/tasks/jobs/http-request.job.ts @@ -1,9 +1,9 @@ -import { HttpService } from '@nestjs/axios' -import { BadRequestException, Injectable } from '@nestjs/common' +import { HttpService } from '@nestjs/axios'; +import { BadRequestException, Injectable } from '@nestjs/common'; -import { LoggerService } from '~/shared/logger/logger.service' +import { LoggerService } from '~/shared/logger/logger.service'; -import { Mission } from '../mission.decorator' +import { Mission } from '../mission.decorator'; /** * Api接口请求类型任务 @@ -13,7 +13,7 @@ import { Mission } from '../mission.decorator' export class HttpRequestJob { constructor( private readonly httpService: HttpService, - private readonly logger: LoggerService, + private readonly logger: LoggerService ) {} /** @@ -22,11 +22,10 @@ export class HttpRequestJob { */ async handle(config: unknown): Promise { if (config) { - const result = await this.httpService.request(config) - this.logger.log(result, HttpRequestJob.name) - } - else { - throw new BadRequestException('Http request job param is empty') + const result = await this.httpService.request(config); + this.logger.log(result, HttpRequestJob.name); + } else { + throw new BadRequestException('Http request job param is empty'); } } } diff --git a/src/modules/tasks/jobs/log-clear.job.ts b/src/modules/tasks/jobs/log-clear.job.ts index fe6fa52..dc24f5d 100644 --- a/src/modules/tasks/jobs/log-clear.job.ts +++ b/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'; /** * 管理后台日志清理任务 @@ -13,14 +13,14 @@ import { Mission } from '../mission.decorator' export class LogClearJob { constructor( private loginLogService: LoginLogService, - private taskLogService: TaskLogService, + private taskLogService: TaskLogService ) {} 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/src/modules/tasks/mission.decorator.ts b/src/modules/tasks/mission.decorator.ts index c14c7c9..beef1b1 100644 --- a/src/modules/tasks/mission.decorator.ts +++ b/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/src/modules/tasks/tasks.module.ts b/src/modules/tasks/tasks.module.ts index 9c6b1de..dd7561d 100644 --- a/src/modules/tasks/tasks.module.ts +++ b/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/src/modules/todo/todo.controller.ts b/src/modules/todo/todo.controller.ts index 0cb95d6..ca415d0 100644 --- a/src/modules/todo/todo.controller.ts +++ b/src/modules/todo/todo.controller.ts @@ -1,27 +1,18 @@ -import { - Body, - Controller, - Delete, - Get, - Post, - Put, - Query, - UseGuards, -} from '@nestjs/common' -import { ApiOperation, ApiTags } from '@nestjs/swagger' +import { Body, Controller, Delete, Get, Post, Put, Query, UseGuards } 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 { ApiResult } from '~/common/decorators/api-result.decorator'; +import { IdParam } from '~/common/decorators/id-param.decorator'; -import { Pagination } from '~/helper/paginate/pagination' -import { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator' -import { Resource } from '~/modules/auth/decorators/resource.decorator' +import { Pagination } from '~/helper/paginate/pagination'; +import { Perm, definePermission } 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, TodoQueryDto, TodoUpdateDto } from './todo.dto' -import { TodoService } from './todo.service' +import { TodoDto, TodoQueryDto, TodoUpdateDto } from './todo.dto'; +import { TodoService } from './todo.service'; export const permissions = definePermission('todo', { LIST: 'list', @@ -29,7 +20,7 @@ export const permissions = definePermission('todo', { READ: 'read', UPDATE: 'update', DELETE: 'delete', -} as const) +} as const); @ApiTags('Business - Todo模块') @UseGuards(ResourceGuard) @@ -42,7 +33,7 @@ export class TodoController { @ApiResult({ type: [TodoEntity] }) @Perm(permissions.LIST) async list(@Query() dto: TodoQueryDto): Promise> { - return this.todoService.list(dto) + return this.todoService.list(dto); } @Get(':id') @@ -50,23 +41,22 @@ export class TodoController { @ApiResult({ type: TodoEntity }) @Perm(permissions.READ) async info(@IdParam() id: number): Promise { - return this.todoService.detail(id) + return this.todoService.detail(id); } @Post() @ApiOperation({ summary: '创建Todo' }) @Perm(permissions.CREATE) async create(@Body() dto: TodoDto): Promise { - await this.todoService.create(dto) + await this.todoService.create(dto); } @Put(':id') @ApiOperation({ summary: '更新Todo' }) @Perm(permissions.UPDATE) @Resource(TodoEntity) - async update( - @IdParam() id: number, @Body() dto: TodoUpdateDto): Promise { - await this.todoService.update(id, dto) + async update(@IdParam() id: number, @Body() dto: TodoUpdateDto): Promise { + await this.todoService.update(id, dto); } @Delete(':id') @@ -74,6 +64,6 @@ export class TodoController { @Perm(permissions.DELETE) @Resource(TodoEntity) async delete(@IdParam() id: number): Promise { - await this.todoService.delete(id) + await this.todoService.delete(id); } } diff --git a/src/modules/todo/todo.dto.ts b/src/modules/todo/todo.dto.ts index 8b222b3..9554651 100644 --- a/src/modules/todo/todo.dto.ts +++ b/src/modules/todo/todo.dto.ts @@ -1,12 +1,12 @@ -import { ApiProperty, IntersectionType, PartialType } from '@nestjs/swagger' -import { IsString } from 'class-validator' +import { ApiProperty, IntersectionType, PartialType } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; -import { PagerDto } from '~/common/dto/pager.dto' +import { PagerDto } from '~/common/dto/pager.dto'; export class TodoDto { @ApiProperty({ description: '名称' }) @IsString() - value: string + value: string; } export class TodoUpdateDto extends PartialType(TodoDto) {} diff --git a/src/modules/todo/todo.entity.ts b/src/modules/todo/todo.entity.ts index acc32ad..41b8ffb 100644 --- a/src/modules/todo/todo.entity.ts +++ b/src/modules/todo/todo.entity.ts @@ -1,20 +1,20 @@ -import { ApiProperty } from '@nestjs/swagger' -import { Column, Entity, JoinColumn, ManyToOne, Relation } from 'typeorm' +import { ApiProperty } from '@nestjs/swagger'; +import { Column, Entity, JoinColumn, ManyToOne, Relation } from 'typeorm'; -import { CommonEntity } from '~/common/entity/common.entity' -import { UserEntity } from '~/modules/user/user.entity' +import { CommonEntity } from '~/common/entity/common.entity'; +import { UserEntity } from '~/modules/user/user.entity'; @Entity('todo') export class TodoEntity extends CommonEntity { @Column() @ApiProperty({ description: 'todo' }) - value: string + value: string; @ApiProperty({ description: 'todo' }) @Column({ default: false }) - status: boolean + status: boolean; @ManyToOne(() => UserEntity) @JoinColumn({ name: 'user_id' }) - user: Relation + user: Relation; } diff --git a/src/modules/todo/todo.module.ts b/src/modules/todo/todo.module.ts index 5a3d417..78ee8ff 100644 --- a/src/modules/todo/todo.module.ts +++ b/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/src/modules/todo/todo.service.ts b/src/modules/todo/todo.service.ts index 4fbf35e..26b4e23 100644 --- a/src/modules/todo/todo.service.ts +++ b/src/modules/todo/todo.service.ts @@ -1,46 +1,42 @@ -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, TodoUpdateDto } from './todo.dto' +import { TodoDto, TodoQueryDto, TodoUpdateDto } from './todo.dto'; @Injectable() export class TodoService { constructor( @InjectRepository(TodoEntity) - private todoRepository: Repository, + private todoRepository: Repository ) {} - async list({ - page, - pageSize, - }: TodoQueryDto): Promise> { - return paginate(this.todoRepository, { page, pageSize }) + async list({ page, pageSize }: TodoQueryDto): Promise> { + 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) { - await this.todoRepository.save(dto) + await this.todoRepository.save(dto); } async update(id: number, dto: TodoUpdateDto) { - 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/src/modules/tools/email/email.controller.ts b/src/modules/tools/email/email.controller.ts index d3a1816..6afde9d 100644 --- a/src/modules/tools/email/email.controller.ts +++ b/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.send(to, subject, content, 'html') + const { to, subject, content } = dto; + await this.emailService.send(to, subject, content, 'html'); } } diff --git a/src/modules/tools/email/email.dto.ts b/src/modules/tools/email/email.dto.ts index 8dcca35..b6cd598 100644 --- a/src/modules/tools/email/email.dto.ts +++ b/src/modules/tools/email/email.dto.ts @@ -1,5 +1,5 @@ -import { ApiProperty } from '@nestjs/swagger' -import { IsEmail, IsString } from 'class-validator' +import { ApiProperty } from '@nestjs/swagger'; +import { IsEmail, IsString } from 'class-validator'; /** * 发送邮件 @@ -7,13 +7,13 @@ import { IsEmail, IsString } from 'class-validator' export class EmailSendDto { @ApiProperty({ description: '收件人邮箱' }) @IsEmail() - to: string + to: string; @ApiProperty({ description: '标题' }) @IsString() - subject: string + subject: string; @ApiProperty({ description: '正文' }) @IsString() - content: string + content: string; } diff --git a/src/modules/tools/email/email.module.ts b/src/modules/tools/email/email.module.ts index e9ba5b1..76e689a 100644 --- a/src/modules/tools/email/email.module.ts +++ b/src/modules/tools/email/email.module.ts @@ -1,6 +1,6 @@ -import { Module } from '@nestjs/common' +import { Module } from '@nestjs/common'; -import { EmailController } from './email.controller' +import { EmailController } from './email.controller'; @Module({ imports: [], diff --git a/src/modules/tools/storage/storage.controller.ts b/src/modules/tools/storage/storage.controller.ts index bdd92ba..c7a13a3 100644 --- a/src/modules/tools/storage/storage.controller.ts +++ b/src/modules/tools/storage/storage.controller.ts @@ -1,22 +1,22 @@ -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 { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator' +import { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator'; -import { StorageDeleteDto, StoragePageDto } from './storage.dto' -import { StorageInfo } from './storage.modal' -import { StorageService } from './storage.service' +import { StorageDeleteDto, StoragePageDto } from './storage.dto'; +import { StorageInfo } from './storage.modal'; +import { StorageService } from './storage.service'; export const permissions = definePermission('tool:storage', { LIST: 'list', DELETE: 'delete', -} as const) +} as const); @ApiTags('Tools - 存储模块') @ApiSecurityAuth() @@ -29,13 +29,13 @@ export class StorageController { @ApiResult({ type: [StorageInfo], isPage: true }) @Perm(permissions.LIST) async list(@Query() dto: StoragePageDto): Promise> { - return this.storageService.list(dto) + return this.storageService.list(dto); } @ApiOperation({ summary: '删除文件' }) @Post('delete') @Perm(permissions.DELETE) async delete(@Body() dto: StorageDeleteDto): Promise { - await this.storageService.delete(dto.ids) + await this.storageService.delete(dto.ids); } } diff --git a/src/modules/tools/storage/storage.dto.ts b/src/modules/tools/storage/storage.dto.ts index 5e50398..ed25a7e 100644 --- a/src/modules/tools/storage/storage.dto.ts +++ b/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 { PagerDto } from '~/common/dto/pager.dto' +import { PagerDto } from '~/common/dto/pager.dto'; export class StoragePageDto extends PagerDto { @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/src/modules/tools/storage/storage.entity.ts b/src/modules/tools/storage/storage.entity.ts index c68dcf5..584ec59 100644 --- a/src/modules/tools/storage/storage.entity.ts +++ b/src/modules/tools/storage/storage.entity.ts @@ -1,13 +1,13 @@ -import { ApiProperty } from '@nestjs/swagger' -import { Column, Entity } from 'typeorm' +import { ApiProperty } from '@nestjs/swagger'; +import { Column, Entity } from 'typeorm'; -import { CommonEntity } from '~/common/entity/common.entity' +import { CommonEntity } from '~/common/entity/common.entity'; @Entity({ name: 'tool_storage' }) export class Storage extends CommonEntity { @Column({ type: 'varchar', length: 200, comment: '文件名' }) @ApiProperty({ description: '文件名' }) - name: string + name: string; @Column({ type: 'varchar', @@ -16,25 +16,25 @@ export class Storage extends CommonEntity { 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/src/modules/tools/storage/storage.modal.ts b/src/modules/tools/storage/storage.modal.ts index 203e974..43ede4e 100644 --- a/src/modules/tools/storage/storage.modal.ts +++ b/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/src/modules/tools/storage/storage.module.ts b/src/modules/tools/storage/storage.module.ts index aed7f33..e74ca3f 100644 --- a/src/modules/tools/storage/storage.module.ts +++ b/src/modules/tools/storage/storage.module.ts @@ -1,13 +1,13 @@ -import { Module } from '@nestjs/common' -import { TypeOrmModule } from '@nestjs/typeorm' +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; -import { UserEntity } from '~/modules/user/user.entity' +import { UserEntity } from '~/modules/user/user.entity'; -import { StorageController } from './storage.controller' -import { Storage } from './storage.entity' -import { StorageService } from './storage.service' +import { StorageController } from './storage.controller'; +import { Storage } from './storage.entity'; +import { StorageService } from './storage.service'; -const services = [StorageService] +const services = [StorageService]; @Module({ imports: [TypeOrmModule.forFeature([Storage, UserEntity])], diff --git a/src/modules/tools/storage/storage.service.ts b/src/modules/tools/storage/storage.service.ts index e7ddefa..99c5fb9 100644 --- a/src/modules/tools/storage/storage.service.ts +++ b/src/modules/tools/storage/storage.service.ts @@ -1,16 +1,16 @@ -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/user.entity' -import { deleteFile } from '~/utils' +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/user.entity'; +import { deleteFile } from '~/utils'; -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 { @@ -18,26 +18,26 @@ export class StorageService { @InjectRepository(Storage) private storageRepository: Repository, @InjectRepository(UserEntity) - private userRepository: Repository, + private userRepository: Repository ) {} async create(dto: StorageCreateDto, userId: number): Promise { 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) - }) + items.forEach(el => { + deleteFile(el.path); + }); } async list({ @@ -63,13 +63,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) => { @@ -82,17 +82,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/src/modules/tools/tools.module.ts b/src/modules/tools/tools.module.ts index d22d131..9e8b5e0 100644 --- a/src/modules/tools/tools.module.ts +++ b/src/modules/tools/tools.module.ts @@ -1,21 +1,24 @@ -import { Module } from '@nestjs/common' +import { Module } from '@nestjs/common'; -import { RouterModule } from '@nestjs/core' +import { RouterModule } from '@nestjs/core'; -import { EmailModule } from './email/email.module' -import { StorageModule } from './storage/storage.module' -import { UploadModule } from './upload/upload.module' +import { EmailModule } from './email/email.module'; +import { StorageModule } from './storage/storage.module'; +import { UploadModule } from './upload/upload.module'; -const modules = [StorageModule, EmailModule, UploadModule] +const modules = [StorageModule, EmailModule, UploadModule]; @Module({ - imports: [...modules, RouterModule.register([ - { - path: 'tools', - module: ToolsModule, - children: [...modules], - }, - ])], + imports: [ + ...modules, + RouterModule.register([ + { + path: 'tools', + module: ToolsModule, + children: [...modules], + }, + ]), + ], exports: [...modules], }) export class ToolsModule {} diff --git a/src/modules/tools/upload/file.constraint.ts b/src/modules/tools/upload/file.constraint.ts index dfaa04b..3adcf49 100644 --- a/src/modules/tools/upload/file.constraint.ts +++ b/src/modules/tools/upload/file.constraint.ts @@ -1,45 +1,35 @@ -import { FastifyMultipartBaseOptions, MultipartFile } from '@fastify/multipart' +import { FastifyMultipartBaseOptions, MultipartFile } from '@fastify/multipart'; import { ValidationArguments, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface, registerDecorator, -} 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[] -} +type FileLimit = Pick & { + mimetypes?: string[]; +}; function checkFileAndLimit(file: MultipartFile, limits: FileLimit = {}) { - if (!('mimetype' in file)) - return false - if (limits.mimetypes && !limits.mimetypes.includes(file.mimetype)) - return false - if ( - has(file, '_buf') - && Buffer.byteLength((file as any)._buf) > limits.fileSize - ) - return false - return true + if (!('mimetype' in file)) return false; + if (limits.mimetypes && !limits.mimetypes.includes(file.mimetype)) return false; + if (has(file, '_buf') && Buffer.byteLength((file as any)._buf) > limits.fileSize) 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 - if (filesLimit > 0 && isArray(values) && values.length > filesLimit) - return false - return checkFileAndLimit(value, limits) + 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); } defaultMessage(_args: ValidationArguments) { - return `The file which to upload's conditions are not met` + return `The file which to upload's conditions are not met`; } } @@ -48,10 +38,7 @@ export class FileConstraint implements ValidatorConstraintInterface { * @param limits 限制选项 * @param validationOptions class-validator选项 */ -export function IsFile( - limits?: FileLimit, - validationOptions?: ValidationOptions, -) { +export function IsFile(limits?: FileLimit, validationOptions?: ValidationOptions) { return (object: Record, propertyName: string) => { registerDecorator({ target: object.constructor, @@ -59,6 +46,6 @@ export function IsFile( options: validationOptions, constraints: [limits], validator: FileConstraint, - }) - } + }); + }; } diff --git a/src/modules/tools/upload/upload.controller.ts b/src/modules/tools/upload/upload.controller.ts index 8a73704..e501d68 100644 --- a/src/modules/tools/upload/upload.controller.ts +++ b/src/modules/tools/upload/upload.controller.ts @@ -1,18 +1,18 @@ -import { BadRequestException, Controller, Post, Req } from '@nestjs/common' -import { ApiBody, ApiConsumes, ApiOperation, ApiTags } from '@nestjs/swagger' -import { FastifyRequest } from 'fastify' +import { BadRequestException, Controller, Post, Req } from '@nestjs/common'; +import { ApiBody, ApiConsumes, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { FastifyRequest } from 'fastify'; -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 { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator' +import { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator'; -import { FileUploadDto } from './upload.dto' -import { UploadService } from './upload.service' +import { FileUploadDto } from './upload.dto'; +import { UploadService } from './upload.service'; export const permissions = definePermission('upload', { UPLOAD: 'upload', -} as const) +} as const); @ApiSecurityAuth() @ApiTags('Tools - 上传模块') @@ -28,10 +28,9 @@ export class UploadController { type: FileUploadDto, }) async upload(@Req() req: FastifyRequest, @AuthUser() user: IAuthUser) { - if (!req.isMultipart()) - throw new BadRequestException('Request is not multipart') + if (!req.isMultipart()) throw new BadRequestException('Request is not multipart'); - const file = await req.file() + const file = await req.file(); // https://github.com/fastify/fastify-multipart // const parts = req.files() @@ -39,15 +38,14 @@ export class UploadController { // console.log(part.file) 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('上传失败') + }; + } catch (error) { + console.log(error); + throw new BadRequestException('上传失败'); } } } diff --git a/src/modules/tools/upload/upload.dto.ts b/src/modules/tools/upload/upload.dto.ts index 3ab9a01..1bf0400 100644 --- a/src/modules/tools/upload/upload.dto.ts +++ b/src/modules/tools/upload/upload.dto.ts @@ -1,27 +1,21 @@ -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: Buffer, format: 'binary', description: '文件' }) @IsDefined() @IsFile( { - mimetypes: [ - 'image/png', - 'image/gif', - 'image/jpeg', - 'image/webp', - 'image/svg+xml', - ], + mimetypes: ['image/png', 'image/gif', 'image/jpeg', 'image/webp', 'image/svg+xml'], fileSize: 1024 * 1024 * 10, }, { message: '文件类型不正确', - }, + } ) - file: MultipartFile + file: MultipartFile; } diff --git a/src/modules/tools/upload/upload.module.ts b/src/modules/tools/upload/upload.module.ts index 6674739..b5a83df 100644 --- a/src/modules/tools/upload/upload.module.ts +++ b/src/modules/tools/upload/upload.module.ts @@ -1,11 +1,11 @@ -import { Module, forwardRef } from '@nestjs/common' +import { Module, forwardRef } from '@nestjs/common'; -import { StorageModule } from '../storage/storage.module' +import { StorageModule } from '../storage/storage.module'; -import { UploadController } from './upload.controller' -import { UploadService } from './upload.service' +import { UploadController } from './upload.controller'; +import { UploadService } from './upload.service'; -const services = [UploadService] +const services = [UploadService]; @Module({ imports: [forwardRef(() => StorageModule)], diff --git a/src/modules/tools/upload/upload.service.ts b/src/modules/tools/upload/upload.service.ts index 792448f..7e42663 100644 --- a/src/modules/tools/upload/upload.service.ts +++ b/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,30 +13,29 @@ import { getFileType, getSize, saveLocalFile, -} from '~/utils/file.util' +} from '~/utils/file.util'; @Injectable() export class UploadService { constructor( @InjectRepository(Storage) - private storageRepository: Repository, + private storageRepository: Repository ) {} /** * 保存文件上传记录 */ 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/src/modules/user/dto/password.dto.ts b/src/modules/user/dto/password.dto.ts index 3a6d97b..6b5e30c 100644 --- a/src/modules/user/dto/password.dto.ts +++ b/src/modules/user/dto/password.dto.ts @@ -1,5 +1,5 @@ -import { ApiProperty } from '@nestjs/swagger' -import { IsString, Matches, MaxLength, MinLength } from 'class-validator' +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, Matches, MaxLength, MinLength } from 'class-validator'; export class PasswordUpdateDto { @ApiProperty({ description: '旧密码' }) @@ -7,13 +7,13 @@ 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 { @@ -26,7 +26,7 @@ export class UserPasswordDto { @Matches(/^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Za-z])\S*$/, { message: '密码格式不正确', }) - password: string + password: string; } export class UserExistDto { @@ -35,5 +35,5 @@ export class UserExistDto { @Matches(/^[a-zA-Z0-9_-]{4,16}$/) @MinLength(6) @MaxLength(20) - username: string + username: string; } diff --git a/src/modules/user/dto/user.dto.ts b/src/modules/user/dto/user.dto.ts index 60a44fa..17bb5b6 100644 --- a/src/modules/user/dto/user.dto.ts +++ b/src/modules/user/dto/user.dto.ts @@ -1,5 +1,5 @@ -import { ApiProperty, IntersectionType, PartialType } from '@nestjs/swagger' -import { Type } from 'class-transformer' +import { ApiProperty, IntersectionType, PartialType } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; import { ArrayMaxSize, ArrayMinSize, @@ -13,57 +13,57 @@ import { MaxLength, MinLength, ValidateIf, -} from 'class-validator' -import { isEmpty } from 'lodash' +} from 'class-validator'; +import { isEmpty } from 'lodash'; -import { PagerDto } from '~/common/dto/pager.dto' +import { PagerDto } from '~/common/dto/pager.dto'; export class UserDto { @ApiProperty({ description: '头像' }) @IsOptional() @IsString() - avatar?: string + avatar?: string; @ApiProperty({ description: '登录账号', example: 'admin' }) @IsString() @Matches(/^[a-z0-9A-Z\W_]+$/) @MinLength(1) @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: 'admin' }) @IsOptional() @IsString() - nickname: string + nickname: string; @ApiProperty({ description: '邮箱', example: 'bqy.dev@qq.com' }) @IsEmail() @ValidateIf(o => !isEmpty(o.email)) - email: string + email: string; @ApiProperty({ description: '手机号' }) @IsOptional() @IsString() - phone?: string + phone?: string; @ApiProperty({ description: 'QQ' }) @IsOptional() @@ -71,16 +71,16 @@ 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 UserUpdateDto extends PartialType(UserDto) {} @@ -89,11 +89,10 @@ export class UserQueryDto extends IntersectionType(PagerDto, PartialTyp @ApiProperty({ description: '归属大区', example: 1, required: false }) @IsInt() @IsOptional() - deptId?: number + deptId?: number; @ApiProperty({ description: '状态', example: 0, required: false }) @IsInt() @IsOptional() - status?: number - + status?: number; } diff --git a/src/modules/user/user.controller.ts b/src/modules/user/user.controller.ts index 7b4fd1f..d462edb 100644 --- a/src/modules/user/user.controller.ts +++ b/src/modules/user/user.controller.ts @@ -1,17 +1,27 @@ -import { Body, Controller, Delete, Get, Param, ParseArrayPipe, Post, Put, Query } from '@nestjs/common' -import { ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger' +import { + Body, + Controller, + Delete, + Get, + Param, + ParseArrayPipe, + Post, + Put, + Query, +} from '@nestjs/common'; +import { ApiOperation, ApiParam, 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 { MenuService } from '~/modules/system/menu/menu.service' +import { ApiResult } from '~/common/decorators/api-result.decorator'; +import { IdParam } from '~/common/decorators/id-param.decorator'; +import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator'; +import { MenuService } from '~/modules/system/menu/menu.service'; -import { Perm, definePermission } from '../auth/decorators/permission.decorator' +import { Perm, definePermission } from '../auth/decorators/permission.decorator'; -import { UserPasswordDto } from './dto/password.dto' -import { UserDto, UserQueryDto, UserUpdateDto } from './dto/user.dto' -import { UserEntity } from './user.entity' -import { UserService } from './user.service' +import { UserPasswordDto } from './dto/password.dto'; +import { UserDto, UserQueryDto, UserUpdateDto } from './dto/user.dto'; +import { UserEntity } from './user.entity'; +import { UserService } from './user.service'; export const permissions = definePermission('system:user', { LIST: 'list', @@ -22,7 +32,7 @@ export const permissions = definePermission('system:user', { PASSWORD_UPDATE: 'password:update', PASSWORD_RESET: 'pass:reset', -} as const) +} as const); @ApiTags('System - 用户模块') @ApiSecurityAuth() @@ -30,7 +40,7 @@ export const permissions = definePermission('system:user', { export class UserController { constructor( private userService: UserService, - private menuService: MenuService, + private menuService: MenuService ) {} @Get() @@ -38,45 +48,50 @@ export class UserController { @ApiResult({ type: [UserEntity], isPage: true }) @Perm(permissions.LIST) async list(@Query() dto: UserQueryDto) { - return this.userService.list(dto) + return this.userService.list(dto); } @Get(':id') @ApiOperation({ summary: '查询用户' }) @Perm(permissions.READ) async read(@IdParam() id: number) { - return this.userService.info(id) + return this.userService.info(id); } @Post() @ApiOperation({ summary: '新增用户' }) @Perm(permissions.CREATE) async create(@Body() dto: UserDto): Promise { - await this.userService.create(dto) + await this.userService.create(dto); } @Put(':id') @ApiOperation({ summary: '更新用户' }) @Perm(permissions.UPDATE) - async update( - @IdParam() id: number, @Body() dto: UserUpdateDto): Promise { - await this.userService.update(id, dto) - await this.menuService.refreshPerms(id) + async update(@IdParam() id: number, @Body() dto: UserUpdateDto): Promise { + await this.userService.update(id, dto); + await this.menuService.refreshPerms(id); } @Delete(':id') @ApiOperation({ summary: '删除用户' }) - @ApiParam({ name: 'id', type: String, schema: { oneOf: [{ type: 'string' }, { type: 'number' }] } }) + @ApiParam({ + name: 'id', + type: String, + schema: { oneOf: [{ type: 'string' }, { type: 'number' }] }, + }) @Perm(permissions.DELETE) - async delete(@Param('id', new ParseArrayPipe({ items: Number, separator: ',' })) ids: number[]): Promise { - await this.userService.delete(ids) - await this.userService.multiForbidden(ids) + async delete( + @Param('id', new ParseArrayPipe({ items: Number, separator: ',' })) ids: number[] + ): Promise { + await this.userService.delete(ids); + await this.userService.multiForbidden(ids); } @Post(':id/password') @ApiOperation({ summary: '更改用户密码' }) @Perm(permissions.PASSWORD_UPDATE) async password(@IdParam() id: number, @Body() dto: UserPasswordDto): Promise { - await this.userService.forceUpdatePassword(id, dto.password) + await this.userService.forceUpdatePassword(id, dto.password); } } diff --git a/src/modules/user/user.entity.ts b/src/modules/user/user.entity.ts index b1f66b8..7cd2031 100644 --- a/src/modules/user/user.entity.ts +++ b/src/modules/user/user.entity.ts @@ -1,4 +1,4 @@ -import { Exclude } from 'class-transformer' +import { Exclude } from 'class-transformer'; import { Column, Entity, @@ -8,47 +8,47 @@ import { ManyToOne, OneToMany, Relation, -} from 'typeorm' +} from 'typeorm'; -import { CommonEntity } from '~/common/entity/common.entity' +import { CommonEntity } from '~/common/entity/common.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 CommonEntity { @Column({ unique: true }) - username: string + username: string; @Exclude() @Column() - password: string + password: string; @Column({ length: 32 }) - psalt: string + psalt: string; @Column({ 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({ @@ -56,14 +56,14 @@ export class UserEntity extends CommonEntity { joinColumn: { name: 'user_id', referencedColumnName: 'id' }, inverseJoinColumn: { name: 'role_id', referencedColumnName: 'id' }, }) - roles: Relation + roles: Relation; @ManyToOne(() => DeptEntity, dept => dept.users) @JoinColumn({ name: 'dept_id' }) - dept: Relation + dept: Relation; @OneToMany(() => AccessTokenEntity, accessToken => accessToken.user, { cascade: true, }) - accessTokens: Relation + accessTokens: Relation; } diff --git a/src/modules/user/user.model.ts b/src/modules/user/user.model.ts index 1493187..beb082b 100644 --- a/src/modules/user/user.model.ts +++ b/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/src/modules/user/user.module.ts b/src/modules/user/user.module.ts index bd26eae..928c52b 100644 --- a/src/modules/user/user.module.ts +++ b/src/modules/user/user.module.ts @@ -1,24 +1,19 @@ -import { Module } from '@nestjs/common' -import { TypeOrmModule } from '@nestjs/typeorm' +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; -import { MenuModule } from '../system/menu/menu.module' -import { ParamConfigModule } from '../system/param-config/param-config.module' +import { MenuModule } from '../system/menu/menu.module'; +import { ParamConfigModule } from '../system/param-config/param-config.module'; -import { RoleModule } from '../system/role/role.module' +import { RoleModule } from '../system/role/role.module'; -import { UserController } from './user.controller' -import { UserEntity } from './user.entity' -import { UserService } from './user.service' +import { UserController } from './user.controller'; +import { UserEntity } from './user.entity'; +import { UserService } from './user.service'; -const providers = [UserService] +const providers = [UserService]; @Module({ - imports: [ - TypeOrmModule.forFeature([UserEntity]), - RoleModule, - MenuModule, - ParamConfigModule, - ], + imports: [TypeOrmModule.forFeature([UserEntity]), RoleModule, MenuModule, ParamConfigModule], controllers: [UserController], providers: [...providers], exports: [TypeOrmModule, ...providers], diff --git a/src/modules/user/user.service.ts b/src/modules/user/user.service.ts index 294c5ef..36fa5d8 100644 --- a/src/modules/user/user.service.ts +++ b/src/modules/user/user.service.ts @@ -1,33 +1,33 @@ -import { InjectRedis } from '@liaoliaots/nestjs-redis' -import { BadRequestException, Injectable } from '@nestjs/common' -import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm' -import Redis from 'ioredis' -import { isEmpty, isNil, isNumber } from 'lodash' +import { InjectRedis } from '@liaoliaots/nestjs-redis'; +import { BadRequestException, Injectable } from '@nestjs/common'; +import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm'; +import Redis from 'ioredis'; +import { isEmpty, isNil, isNumber } from 'lodash'; -import { EntityManager, In, Like, Repository } from 'typeorm' +import { EntityManager, In, Like, Repository } from 'typeorm'; -import { BusinessException } from '~/common/exceptions/biz.exception' -import { ErrorEnum } from '~/constants/error-code.constant' -import { ROOT_ROLE_ID, SYS_USER_INITPASSWORD } from '~/constants/system.constant' -import { genAuthPVKey, genAuthPermKey, genAuthTokenKey } from '~/helper/genRedisKey' +import { BusinessException } from '~/common/exceptions/biz.exception'; +import { ErrorEnum } from '~/constants/error-code.constant'; +import { ROOT_ROLE_ID, SYS_USER_INITPASSWORD } from '~/constants/system.constant'; +import { genAuthPVKey, genAuthPermKey, genAuthTokenKey } from '~/helper/genRedisKey'; -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/helper/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/helper/qq.service'; -import { md5, randomValue } from '~/utils' +import { md5, randomValue } from '~/utils'; -import { DeptEntity } from '../system/dept/dept.entity' -import { ParamConfigService } from '../system/param-config/param-config.service' -import { RoleEntity } from '../system/role/role.entity' +import { DeptEntity } from '../system/dept/dept.entity'; +import { ParamConfigService } from '../system/param-config/param-config.service'; +import { RoleEntity } from '../system/role/role.entity'; -import { UserStatus } from './constant' -import { PasswordUpdateDto } from './dto/password.dto' -import { UserDto, UserQueryDto, UserUpdateDto } from './dto/user.dto' -import { UserEntity } from './user.entity' -import { AccountInfo } from './user.model' +import { UserStatus } from './constant'; +import { PasswordUpdateDto } from './dto/password.dto'; +import { UserDto, UserQueryDto, UserUpdateDto } from './dto/user.dto'; +import { UserEntity } from './user.entity'; +import { AccountInfo } from './user.model'; @Injectable() export class UserService { @@ -40,7 +40,7 @@ export class UserService { private readonly roleRepository: Repository, @InjectEntityManager() private entityManager: EntityManager, private readonly paramConfigService: ParamConfigService, - private readonly qqService: QQService, + private readonly qqService: QQService ) {} async findUserById(id: number): Promise { @@ -50,7 +50,7 @@ export class UserService { id, status: UserStatus.Enabled, }) - .getOne() + .getOne(); } async findUserByUserName(username: string): Promise { @@ -60,7 +60,7 @@ export class UserService { username, status: UserStatus.Enabled, }) - .getOne() + .getOne(); } /** @@ -72,23 +72,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,73 +95,60 @@ 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) + if (info.qq !== user.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 }) - 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 comparePassword = md5(`${dto.oldPassword}${user.psalt}`) + const comparePassword = md5(`${dto.oldPassword}${user.psalt}`); // 原密码不一致,不允许更改 - if (user.password !== comparePassword) - throw new BusinessException(ErrorEnum.PASSWORD_MISMATCH) + if (user.password !== comparePassword) 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); } /** * 增加系统用户,如果返回false则表示已存在该用户 */ - async create({ - username, - password, - roleIds, - deptId, - ...data - }: UserDto): Promise { + async create({ username, password, roleIds, deptId, ...data }: UserDto): Promise { const exists = await this.userRepository.findOneBy({ username, - }) - if (!isEmpty(exists)) - throw new BusinessException(ErrorEnum.SYSTEM_USER_EXISTS) + }); + if (!isEmpty(exists)) throw new BusinessException(ErrorEnum.SYSTEM_USER_EXISTS); - await this.entityManager.transaction(async (manager) => { - const salt = randomValue(32) + await this.entityManager.transaction(async manager => { + const salt = randomValue(32); if (!password) { - const initPassword = await this.paramConfigService.findValueByKey( - SYS_USER_INITPASSWORD, - ) - password = md5(`${initPassword ?? '123456'}${salt}`) - } - else { - password = md5(`${password ?? '123456'}${salt}`) + const initPassword = await this.paramConfigService.findValueByKey(SYS_USER_INITPASSWORD); + password = md5(`${initPassword ?? '123456'}${salt}`); + } else { + password = md5(`${password ?? '123456'}${salt}`); } const u = manager.create(UserEntity, { username, @@ -172,11 +157,11 @@ export class UserService { psalt: salt, roles: await this.roleRepository.findBy({ id: In(roleIds) }), dept: await DeptEntity.findOneBy({ id: deptId }), - }) + }); - const result = await manager.save(u) - return result - }) + const result = await manager.save(u); + return result; + }); } /** @@ -184,41 +169,36 @@ export class UserService { */ async update( id: number, - { password, deptId, roleIds, status, ...data }: UserUpdateDto, + { password, deptId, roleIds, status, ...data }: UserUpdateDto ): Promise { - await this.entityManager.transaction(async (manager) => { - if (password) - await this.forceUpdatePassword(id, password) + await this.entityManager.transaction(async manager => { + if (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) + await manager.createQueryBuilder().relation(UserEntity, 'dept').of(id).set(deptId); if (status === 0) { // 禁用状态 - await this.forbidden(id) + await this.forbidden(id); } - }) + }); } /** @@ -231,23 +211,22 @@ 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() - if (userIds.includes(rootUserId)) - throw new BadRequestException('不能删除root用户!') + const rootUserId = await this.findRootUserId(); + if (userIds.includes(rootUserId)) throw new BadRequestException('不能删除root用户!'); - await this.userRepository.delete(userIds) + await this.userRepository.delete(userIds); } /** @@ -256,8 +235,8 @@ export class UserService { async findRootUserId(): Promise { const user = await this.userRepository.findOneBy({ roles: { id: ROOT_ROLE_ID }, - }) - return user.id + }); + return user.id; } /** @@ -282,24 +261,23 @@ export class UserService { ...(nickname ? { nickname: Like(`%${nickname}%`) } : null), ...(email ? { email: Like(`%${email}%`) } : null), ...(isNumber(status) ? { status } : null), - }) + }); - if (deptId) - queryBuilder.andWhere('dept.id = :deptId', { deptId }) + if (deptId) queryBuilder.andWhere('dept.id = :deptId', { deptId }); return paginate(queryBuilder, { page, pageSize, - }) + }); } /** * 禁用用户 */ async forbidden(uid: number): Promise { - await this.redis.del(genAuthPVKey(uid)) - await this.redis.del(genAuthTokenKey(uid)) - await this.redis.del(genAuthPermKey(uid)) + await this.redis.del(genAuthPVKey(uid)); + await this.redis.del(genAuthTokenKey(uid)); + await this.redis.del(genAuthPermKey(uid)); } /** @@ -307,17 +285,17 @@ export class UserService { */ async multiForbidden(uids: number[]): Promise { if (uids) { - const pvs: string[] = [] - const ts: string[] = [] - const ps: string[] = [] - uids.forEach((uid) => { - pvs.push(genAuthPVKey(uid)) - ts.push(genAuthTokenKey(uid)) - ps.push(genAuthPermKey(uid)) - }) - await this.redis.del(pvs) - await this.redis.del(ts) - await this.redis.del(ps) + const pvs: string[] = []; + const ts: string[] = []; + const ps: string[] = []; + uids.forEach(uid => { + pvs.push(genAuthPVKey(uid)); + ts.push(genAuthTokenKey(uid)); + ps.push(genAuthPermKey(uid)); + }); + await this.redis.del(pvs); + await this.redis.del(ts); + await this.redis.del(ps); } } @@ -326,20 +304,18 @@ export class UserService { */ async upgradePasswordV(id: number): Promise { // admin:passwordVersion:${param.id} - const v = await this.redis.get(genAuthPVKey(id)) - if (!isEmpty(v)) - await this.redis.set(genAuthPVKey(id), Number.parseInt(v) + 1) + const v = await this.redis.get(genAuthPVKey(id)); + if (!isEmpty(v)) await this.redis.set(genAuthPVKey(id), Number.parseInt(v) + 1); } /** * 判断用户名是否存在 */ async exist(username: string) { - const user = await this.userRepository.findOneBy({ username }) - if (isNil(user)) - throw new BusinessException(ErrorEnum.SYSTEM_USER_EXISTS) + const user = await this.userRepository.findOneBy({ username }); + if (isNil(user)) throw new BusinessException(ErrorEnum.SYSTEM_USER_EXISTS); - return true + return true; } /** @@ -348,25 +324,24 @@ 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) + }); + if (!isEmpty(exists)) throw new BusinessException(ErrorEnum.SYSTEM_USER_EXISTS); - await this.entityManager.transaction(async (manager) => { - const salt = randomValue(32) + await this.entityManager.transaction(async manager => { + 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/src/repl.ts b/src/repl.ts index 649d7ba..2a2a177 100644 --- a/src/repl.ts +++ b/src/repl.ts @@ -1,12 +1,11 @@ -import { repl } from '@nestjs/core' +import { repl } from '@nestjs/core'; -import { AppModule } from './app.module' +import { AppModule } from './app.module'; async function bootstrap() { - const replServer = await repl(AppModule) - replServer.setupHistory('.nestjs_repl_history', (err) => { - if (err) - console.error(err) - }) + const replServer = await repl(AppModule); + replServer.setupHistory('.nestjs_repl_history', err => { + if (err) console.error(err); + }); } -bootstrap() +bootstrap(); diff --git a/src/setup-swagger.ts b/src/setup-swagger.ts index 4858584..d80d52e 100644 --- a/src/setup-swagger.ts +++ b/src/setup-swagger.ts @@ -1,27 +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 { CommonEntity } from './common/entity/common.entity' -import { ResOp, TreeResult } from './common/model/response.model' -import { ConfigKeyPaths, IAppConfig, ISwaggerConfig } from './config' -import { Pagination } from './helper/paginate/pagination' +import { API_SECURITY_AUTH } from './common/decorators/swagger.decorator'; +import { CommonEntity } from './common/entity/common.entity'; +import { ResOp, TreeResult } from './common/model/response.model'; +import { ConfigKeyPaths, IAppConfig, ISwaggerConfig } from './config'; +import { Pagination } from './helper/paginate/pagination'; export function setupSwagger( app: INestApplication, - configService: ConfigService, + 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, { @@ -29,16 +28,16 @@ export function setupSwagger( type: 'apiKey', in: 'header', name: 'Authorization', - }) + }); const document = SwaggerModule.createDocument(app, documentBuilder.build(), { ignoreGlobalPrefix: false, extraModels: [CommonEntity, 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/src/shared/database/constraints/entity-exist.constraint.ts b/src/shared/database/constraints/entity-exist.constraint.ts index 2ce89cd..3f482b7 100644 --- a/src/shared/database/constraints/entity-exist.constraint.ts +++ b/src/shared/database/constraints/entity-exist.constraint.ts @@ -1,17 +1,17 @@ -import { Injectable } from '@nestjs/common' +import { Injectable } from '@nestjs/common'; import { ValidationArguments, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface, registerDecorator, -} from 'class-validator' -import { DataSource, ObjectType, Repository } from 'typeorm' +} from 'class-validator'; +import { DataSource, ObjectType, Repository } from 'typeorm'; interface Condition { - entity: ObjectType + entity: ObjectType; // 如果没有指定字段则使用当前验证的属性作为查询依据 - field?: string + field?: string; } /** @@ -23,32 +23,29 @@ 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) - } - else { + 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!' + if (!args.constraints[0]) 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!`; } } @@ -59,17 +56,17 @@ export class EntityExistConstraint implements ValidatorConstraintInterface { */ function IsEntityExist( entity: ObjectType, - validationOptions?: ValidationOptions, -): (object: Record, propertyName: string) => void + validationOptions?: ValidationOptions +): (object: Record, propertyName: string) => void; function IsEntityExist( - condition: { entity: ObjectType, field?: string }, - validationOptions?: ValidationOptions, -): (object: Record, propertyName: string) => void + condition: { entity: ObjectType; field?: string }, + validationOptions?: ValidationOptions +): (object: Record, propertyName: string) => void; function IsEntityExist( - condition: ObjectType | { entity: ObjectType, field?: string }, - validationOptions?: ValidationOptions, + condition: ObjectType | { entity: ObjectType; field?: string }, + validationOptions?: ValidationOptions ): (object: Record, propertyName: string) => void { return (object: Record, propertyName: string) => { registerDecorator({ @@ -78,8 +75,8 @@ function IsEntityExist( options: validationOptions, constraints: [condition], validator: EntityExistConstraint, - }) - } + }); + }; } -export { IsEntityExist } +export { IsEntityExist }; diff --git a/src/shared/database/constraints/unique.constraint.ts b/src/shared/database/constraints/unique.constraint.ts index 65fa562..2f3450b 100644 --- a/src/shared/database/constraints/unique.constraint.ts +++ b/src/shared/database/constraints/unique.constraint.ts @@ -1,18 +1,18 @@ -import { Injectable } from '@nestjs/common' +import { Injectable } from '@nestjs/common'; import { ValidationArguments, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface, registerDecorator, -} 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'; interface Condition { - entity: ObjectType + entity: ObjectType; // 如果没有指定字段则使用当前验证的属性作为查询依据 - field?: string + field?: string; } /** @@ -27,40 +27,36 @@ 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) { + }) + ); + } catch (err) { // 如果数据库操作异常则验证失败 - return false + return false; } } defaultMessage(args: ValidationArguments) { - const { entity, property } = args.constraints[0] - const queryProperty = property ?? args.property - if (!(args.object as any).getManager) - return 'getManager function not been found!' + const { entity, property } = args.constraints[0]; + const queryProperty = property ?? args.property; + if (!(args.object as any).getManager) return 'getManager function not been found!'; - if (!entity) - return 'Model not been specified!' + if (!entity) return 'Model not been specified!'; - return `${queryProperty} of ${entity.name} must been unique!` + return `${queryProperty} of ${entity.name} must been unique!`; } } @@ -71,18 +67,15 @@ export class UniqueConstraint implements ValidatorConstraintInterface { */ function IsUnique( entity: ObjectType, - validationOptions?: ValidationOptions, -): (object: Record, propertyName: string) => void + validationOptions?: ValidationOptions +): (object: Record, propertyName: string) => void; function IsUnique( condition: Condition, - validationOptions?: ValidationOptions, -): (object: Record, propertyName: string) => void + validationOptions?: ValidationOptions +): (object: Record, propertyName: string) => void; -function IsUnique( - params: ObjectType | Condition, - validationOptions?: ValidationOptions, -) { +function IsUnique(params: ObjectType | Condition, validationOptions?: ValidationOptions) { return (object: Record, propertyName: string) => { registerDecorator({ target: object.constructor, @@ -90,8 +83,8 @@ function IsUnique( options: validationOptions, constraints: [params], validator: UniqueConstraint, - }) - } + }); + }; } -export { IsUnique } +export { IsUnique }; diff --git a/src/shared/database/database.module.ts b/src/shared/database/database.module.ts index acb5526..528df0f 100644 --- a/src/shared/database/database.module.ts +++ b/src/shared/database/database.module.ts @@ -1,32 +1,31 @@ -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 { DataSource, LoggerOptions } from 'typeorm' +import { DataSource, LoggerOptions } from 'typeorm'; -import { ConfigKeyPaths, IDatabaseConfig } from '~/config' +import { ConfigKeyPaths, IDatabaseConfig } from '~/config'; -import { env } from '~/global/env' +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({ inject: [ConfigService], 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) - } - catch { + loggerOptions = JSON.parse(loggerOptions); + } catch { // ignore } @@ -35,13 +34,13 @@ const providers = [EntityExistConstraint, UniqueConstraint] autoLoadEntities: true, logging: loggerOptions, logger: new TypeORMLogger(loggerOptions), - } + }; }, // dataSource receives the configured DataSourceOptions // and returns a Promise. - dataSourceFactory: async (options) => { - const dataSource = await new DataSource(options).initialize() - return dataSource + dataSourceFactory: async options => { + const dataSource = await new DataSource(options).initialize(); + return dataSource; }, }), ], diff --git a/src/shared/database/typeorm-logger.ts b/src/shared/database/typeorm-logger.ts index 434932b..8f425f7 100644 --- a/src/shared/database/typeorm-logger.ts +++ b/src/shared/database/typeorm-logger.ts @@ -1,91 +1,77 @@ -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 + const sql = + query + + (parameters && parameters.length ? ` -- PARAMETERS: ${this.stringifyParams(parameters)}` - : '') + : ''); - this.logger.log(`[QUERY]: ${sql}`) + this.logger.log(`[QUERY]: ${sql}`); } logQueryError( error: string | Error, query: string, parameters?: any[], - _queryRunner?: QueryRunner, + _queryRunner?: QueryRunner ) { - if (!this.isEnable('error')) - return + if (!this.isEnable('error')) return; - const sql - = query - + (parameters && parameters.length + 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( - time: number, - query: string, - parameters?: any[], - _queryRunner?: QueryRunner, - ) { - const sql - = query - + (parameters && parameters.length + logQuerySlow(time: number, query: string, parameters?: any[], _queryRunner?: QueryRunner) { + const sql = + 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( - level: 'warn' | 'info' | 'log', - message: any, - _queryRunner?: QueryRunner, - ) { - if (!this.isEnable(level)) - return + log(level: 'warn' | 'info' | 'log', message: any, _queryRunner?: QueryRunner) { + 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; } } @@ -95,11 +81,10 @@ export class TypeORMLogger implements ITypeORMLogger { */ private stringifyParams(parameters: any[]) { try { - return JSON.stringify(parameters) - } - catch (error) { + return JSON.stringify(parameters); + } catch (error) { // most probably circular objects in parameters - return parameters + return parameters; } } @@ -107,12 +92,12 @@ export class TypeORMLogger implements ITypeORMLogger { * check enbale log */ private isEnable( - level: 'query' | 'schema' | 'error' | 'warn' | 'info' | 'log' | 'migration', + level: 'query' | 'schema' | 'error' | 'warn' | 'info' | 'log' | 'migration' ): boolean { return ( - this.options === 'all' - || this.options === true - || (Array.isArray(this.options) && this.options.includes(level)) - ) + this.options === 'all' || + this.options === true || + (Array.isArray(this.options) && this.options.includes(level)) + ); } } diff --git a/src/shared/helper/cron.service.ts b/src/shared/helper/cron.service.ts index 94d031b..09fb895 100644 --- a/src/shared/helper/cron.service.ts +++ b/src/shared/helper/cron.service.ts @@ -1,48 +1,44 @@ -import { Injectable, Logger } from '@nestjs/common' -import { ConfigService } from '@nestjs/config' -import { CronExpression } from '@nestjs/schedule' -import dayjs from 'dayjs' +import { Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { CronExpression } from '@nestjs/schedule'; +import dayjs from 'dayjs'; -import { LessThan } from 'typeorm' +import { LessThan } from 'typeorm'; -import { CronOnce } from '~/common/decorators/cron-once.decorator' -import { ConfigKeyPaths } from '~/config' -import { AccessTokenEntity } from '~/modules/auth/entities/access-token.entity' +import { CronOnce } from '~/common/decorators/cron-once.decorator'; +import { ConfigKeyPaths } from '~/config'; +import { AccessTokenEntity } from '~/modules/auth/entities/access-token.entity'; @Injectable() export class CronService { - private logger: Logger = new Logger(CronService.name) - constructor( - private readonly configService: ConfigService, - ) {} + private logger: Logger = new Logger(CronService.name); + constructor(private readonly configService: ConfigService) {} @CronOnce(CronExpression.EVERY_DAY_AT_MIDNIGHT) async deleteExpiredJWT() { - this.logger.log('--> 开始扫表,清除过期的 token') + this.logger.log('--> 开始扫表,清除过期的 token'); const expiredTokens = await AccessTokenEntity.find({ where: { expired_at: LessThan(new Date()), }, - }) + }); - let deleteCount = 0 + let deleteCount = 0; await Promise.all( - expiredTokens.map(async (token) => { - const { value, created_at } = token + expiredTokens.map(async token => { + const { value, created_at } = token; - await AccessTokenEntity.remove(token) + await AccessTokenEntity.remove(token); this.logger.debug( - `--> 删除过期的 token:${value}, 签发于 ${dayjs(created_at).format( - 'YYYY-MM-DD H:mm:ss', - )}`, - ) + `--> 删除过期的 token:${value}, 签发于 ${dayjs(created_at).format('YYYY-MM-DD H:mm:ss')}` + ); - deleteCount += 1 - }), - ) + deleteCount += 1; + }) + ); - this.logger.log(`--> 删除了 ${deleteCount} 个过期的 token`) + this.logger.log(`--> 删除了 ${deleteCount} 个过期的 token`); } } diff --git a/src/shared/helper/helper.module.ts b/src/shared/helper/helper.module.ts index 1261c7b..dc8e166 100644 --- a/src/shared/helper/helper.module.ts +++ b/src/shared/helper/helper.module.ts @@ -1,12 +1,9 @@ -import { Global, Module, type Provider } from '@nestjs/common' +import { Global, Module, type Provider } from '@nestjs/common'; -import { CronService } from './cron.service' -import { QQService } from './qq.service' +import { CronService } from './cron.service'; +import { QQService } from './qq.service'; -const providers: Provider[] = [ - CronService, - QQService, -] +const providers: Provider[] = [CronService, QQService]; @Global() @Module({ diff --git a/src/shared/helper/qq.service.ts b/src/shared/helper/qq.service.ts index 9c5f788..568cef4 100644 --- a/src/shared/helper/qq.service.ts +++ b/src/shared/helper/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 { @@ -7,13 +7,13 @@ export class QQService { async getNickname(qq: string | number) { const { data } = await this.http.axiosRef.get( - `https://users.qzone.qq.com/fcg-bin/cgi_get_portrait.fcg?uins=${qq}`, - ) - return data + `https://users.qzone.qq.com/fcg-bin/cgi_get_portrait.fcg?uins=${qq}` + ); + return data; } async getAvater(qq: string | number) { // https://thirdqq.qlogo.cn/headimg_dl?dst_uin=1743369777&spec=640&img_type=jpg - return `https://thirdqq.qlogo.cn/g?b=qq&s=100&nk=${qq}` + return `https://thirdqq.qlogo.cn/g?b=qq&s=100&nk=${qq}`; } } diff --git a/src/shared/logger/logger.module.ts b/src/shared/logger/logger.module.ts index 3757496..0cd7a70 100644 --- a/src/shared/logger/logger.module.ts +++ b/src/shared/logger/logger.module.ts @@ -1,6 +1,6 @@ -import { Module } from '@nestjs/common' +import { Module } from '@nestjs/common'; -import { LoggerService } from './logger.service' +import { LoggerService } from './logger.service'; @Module({}) export class LoggerModule { @@ -10,6 +10,6 @@ export class LoggerModule { module: LoggerModule, providers: [LoggerService], exports: [LoggerService], - } + }; } } diff --git a/src/shared/logger/logger.service.ts b/src/shared/logger/logger.service.ts index f2856e1..ce477cd 100644 --- a/src/shared/logger/logger.service.ts +++ b/src/shared/logger/logger.service.ts @@ -1,13 +1,13 @@ -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 { config, createLogger, format, transports } from 'winston' +import { config, createLogger, format, transports } from 'winston'; -import 'winston-daily-rotate-file' +import 'winston-daily-rotate-file'; -import { ConfigKeyPaths } from '~/config' +import { ConfigKeyPaths } from '~/config'; export enum LogLevel { ERROR = 'error', @@ -19,33 +19,29 @@ export enum LogLevel { @Injectable() export class LoggerService extends ConsoleLogger { - private winstonLogger: WinstonLogger + private winstonLogger: WinstonLogger; constructor( context: string, options: ConsoleLoggerOptions, - private configService: ConfigService, + private configService: ConfigService ) { - super(context, options) - this.initWinston() + super(context, options); + this.initWinston(); } protected get level(): LogLevel { - return this.configService.get('app.logger.level', { infer: true }) as LogLevel + return this.configService.get('app.logger.level', { infer: true }) as LogLevel; } protected get maxFiles(): number { - return this.configService.get('app.logger.maxFiles', { infer: true }) + return this.configService.get('app.logger.maxFiles', { infer: true }); } protected initWinston(): void { this.winstonLogger = createLogger({ levels: config.npm.levels, - format: format.combine( - format.errors({ stack: true }), - format.timestamp(), - format.json(), - ), + format: format.combine(format.errors({ stack: true }), format.timestamp(), format.json()), transports: [ new transports.DailyRotateFile({ level: this.level, @@ -64,7 +60,7 @@ export class LoggerService extends ConsoleLogger { auditFile: 'logs/.audit/app-error.json', }), ], - }) + }); // if (isDev) { // this.winstonLogger.add( @@ -80,36 +76,36 @@ export class LoggerService 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/src/shared/mailer/mailer.module.ts b/src/shared/mailer/mailer.module.ts index 9ee4128..24d1e1f 100644 --- a/src/shared/mailer/mailer.module.ts +++ b/src/shared/mailer/mailer.module.ts @@ -1,17 +1,15 @@ -import { join } from 'node:path' +import { join } from 'node:path'; -import { Module, Provider } from '@nestjs/common' -import { ConfigModule, ConfigService } from '@nestjs/config' -import { MailerModule as NestMailerModule } from '@nestjs-modules/mailer' -import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter' +import { Module, Provider } from '@nestjs/common'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { MailerModule as NestMailerModule } from '@nestjs-modules/mailer'; +import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter'; -import { ConfigKeyPaths, IAppConfig, IMailerConfig } from '~/config' +import { ConfigKeyPaths, IAppConfig, IMailerConfig } from '~/config'; -import { MailerService } from './mailer.service' +import { MailerService } from './mailer.service'; -const providers: Provider[] = [ - MailerService, -] +const providers: Provider[] = [MailerService]; @Module({ imports: [ diff --git a/src/shared/mailer/mailer.service.ts b/src/shared/mailer/mailer.service.ts index 16aba4a..2b5fcf3 100644 --- a/src/shared/mailer/mailer.service.ts +++ b/src/shared/mailer/mailer.service.ts @@ -1,119 +1,97 @@ -import { InjectRedis } from '@liaoliaots/nestjs-redis' -import { Inject, Injectable } from '@nestjs/common' +import { InjectRedis } from '@liaoliaots/nestjs-redis'; +import { Inject, Injectable } from '@nestjs/common'; -import { MailerService as NestMailerService } from '@nestjs-modules/mailer' -import dayjs from 'dayjs' +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 { AppConfig, IAppConfig } from '~/config' -import { ErrorEnum } from '~/constants/error-code.constant' -import { randomValue } from '~/utils' +import { BusinessException } from '~/common/exceptions/biz.exception'; +import { AppConfig, IAppConfig } from '~/config'; +import { ErrorEnum } from '~/constants/error-code.constant'; +import { randomValue } from '~/utils'; @Injectable() export class MailerService { constructor( @Inject(AppConfig.KEY) private appConfig: IAppConfig, @InjectRedis() private redis: Redis, - private mailerService: NestMailerService, + private mailerService: NestMailerService ) {} 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(), - ) + getRemainTime() + ); + await this.redis.set(`ip:${ip}:send:limit-count-day`, ipLimitCountOfDay, 'EX', getRemainTime()); } async checkCode(to, code) { - const ret = await this.redis.get(`captcha:${to}`) - if (ret !== code) - throw new BusinessException(ErrorEnum.INVALID_VERIFICATION_CODE) + const ret = await this.redis.get(`captcha:${to}`); + if (ret !== 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 + let limitCountOfDay: string | number = await this.redis.get(`captcha:${to}:limit-day`); + limitCountOfDay = limitCountOfDay ? Number(limitCountOfDay) : 0; if (limitCountOfDay > LIMIT_TIME) { - throw new BusinessException( - ErrorEnum.MAXIMUM_FIVE_VERIFICATION_CODES_PER_DAY, - ) + 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 + let ipLimitCountOfDay: string | number = await this.redis.get(`ip:${ip}:send:limit-day`); + ipLimitCountOfDay = ipLimitCountOfDay ? Number(ipLimitCountOfDay) : 0; if (ipLimitCountOfDay > LIMIT_TIME) { - throw new BusinessException( - ErrorEnum.MAXIMUM_FIVE_VERIFICATION_CODES_PER_DAY, - ) + throw new BusinessException(ErrorEnum.MAXIMUM_FIVE_VERIFICATION_CODES_PER_DAY); } } - async send( - to, - subject, - content: string, - type: 'text' | 'html' = 'text', - ): Promise { + async send(to, subject, content: string, type: 'text' | 'html' = 'text'): Promise { if (type === 'text') { return this.mailerService.sendMail({ to, subject, text: content, - }) - } - else { + }); + } else { return this.mailerService.sendMail({ to, subject, html: content, - }) + }); } } async sendVerificationCode(to, code = randomValue(4, '1234567890')) { - const subject = `[${this.appConfig.name}] 验证码` + const subject = `[${this.appConfig.name}] 验证码`; try { await this.mailerService.sendMail({ @@ -123,17 +101,16 @@ export class MailerService { context: { code, }, - }) - } - catch (error) { - console.log(error) - throw new BusinessException(ErrorEnum.VERIFICATION_CODE_SEND_FAILED) + }); + } catch (error) { + console.log(error); + throw new BusinessException(ErrorEnum.VERIFICATION_CODE_SEND_FAILED); } return { to, code, - } + }; } // async sendUserConfirmation(user: UserEntity, token: string) { diff --git a/src/shared/redis/cache.service.ts b/src/shared/redis/cache.service.ts index 90c8707..6edd80e 100644 --- a/src/shared/redis/cache.service.ts +++ b/src/shared/redis/cache.service.ts @@ -1,68 +1,67 @@ -import { CACHE_MANAGER } from '@nestjs/cache-manager' -import { Inject, Injectable } from '@nestjs/common' -import { Emitter } from '@socket.io/redis-emitter' -import { Cache } from 'cache-manager' -import type { Redis } from 'ioredis' +import { CACHE_MANAGER } from '@nestjs/cache-manager'; +import { Inject, Injectable } from '@nestjs/common'; +import { Emitter } from '@socket.io/redis-emitter'; +import { Cache } from 'cache-manager'; +import type { Redis } from 'ioredis'; -import { RedisIoAdapterKey } from '~/common/adapters/socket.adapter' +import { RedisIoAdapterKey } from '~/common/adapters/socket.adapter'; -import { API_CACHE_PREFIX } from '~/constants/cache.constant' -import { getRedisKey } from '~/utils/redis.util' +import { API_CACHE_PREFIX } from '~/constants/cache.constant'; +import { getRedisKey } from '~/utils/redis.util'; // 获取器 -export type TCacheKey = string -export type TCacheResult = Promise +export type TCacheKey = string; +export type TCacheResult = Promise; @Injectable() export class CacheService { - private cache!: Cache + private cache!: Cache; - private ioRedis!: Redis + private ioRedis!: Redis; constructor(@Inject(CACHE_MANAGER) cache: Cache) { - this.cache = cache + this.cache = cache; } private get redisClient(): Redis { // eslint-disable-next-line ts/ban-ts-comment // @ts-expect-error - return this.cache.store.client + return this.cache.store.client; } public get(key: TCacheKey): TCacheResult { - return this.cache.get(key) + return this.cache.get(key); } public set(key: TCacheKey, value: any, milliseconds: number) { - return this.cache.set(key, value, milliseconds) + return this.cache.set(key, value, milliseconds); } public getClient() { - return this.redisClient + return this.redisClient; } - private _emitter: Emitter + private _emitter: Emitter; public get emitter(): Emitter { - if (this._emitter) - return this._emitter + if (this._emitter) return this._emitter; this._emitter = new Emitter(this.redisClient, { key: RedisIoAdapterKey, - }) + }); - return this._emitter + return this._emitter; } public async cleanCatch() { - const redis = this.getClient() - const keys: string[] = await redis.keys(`${API_CACHE_PREFIX}*`) - await Promise.all(keys.map(key => redis.del(key))) + const redis = this.getClient(); + const keys: string[] = await redis.keys(`${API_CACHE_PREFIX}*`); + await Promise.all(keys.map(key => redis.del(key))); } public async cleanAllRedisKey() { - const redis = this.getClient() - const keys: string[] = await redis.keys(getRedisKey('*')) + const redis = this.getClient(); + const keys: string[] = await redis.keys(getRedisKey('*')); - await Promise.all(keys.map(key => redis.del(key))) + await Promise.all(keys.map(key => redis.del(key))); } } diff --git a/src/shared/redis/redis-subpub.ts b/src/shared/redis/redis-subpub.ts index 7634753..4f4934c 100644 --- a/src/shared/redis/redis-subpub.ts +++ b/src/shared/redis/redis-subpub.ts @@ -1,68 +1,65 @@ -import { Logger } from '@nestjs/common' -import IORedis from 'ioredis' -import type { Redis, RedisOptions } from 'ioredis' +import { Logger } from '@nestjs/common'; +import IORedis from 'ioredis'; +import type { Redis, RedisOptions } from 'ioredis'; export class RedisSubPub { - public pubClient: Redis - public subClient: Redis + public pubClient: Redis; + public subClient: Redis; constructor( private redisConfig: RedisOptions, - private channelPrefix: string = 'm-shop-channel#', + private channelPrefix: string = 'm-shop-channel#' ) { - this.init() + this.init(); } public init() { const redisOptions: RedisOptions = { host: this.redisConfig.host, port: this.redisConfig.port, - } + }; - if (this.redisConfig.password) - redisOptions.password = this.redisConfig.password + if (this.redisConfig.password) redisOptions.password = this.redisConfig.password; - const pubClient = new IORedis(redisOptions) - const subClient = pubClient.duplicate() - this.pubClient = pubClient - this.subClient = subClient + const pubClient = new IORedis(redisOptions); + const subClient = pubClient.duplicate(); + this.pubClient = pubClient; + this.subClient = subClient; } public async publish(event: string, data: any) { - const channel = this.channelPrefix + event - const _data = JSON.stringify(data) - if (event !== 'log') - Logger.debug(`发布事件:${channel} <- ${_data}`, RedisSubPub.name) + const channel = this.channelPrefix + event; + const _data = JSON.stringify(data); + if (event !== 'log') Logger.debug(`发布事件:${channel} <- ${_data}`, RedisSubPub.name); - await this.pubClient.publish(channel, _data) + await this.pubClient.publish(channel, _data); } - private ctc = new WeakMap void>() + private ctc = new WeakMap void>(); public async subscribe(event: string, callback: (data: any) => void) { - const myChannel = this.channelPrefix + event - this.subClient.subscribe(myChannel) + const myChannel = this.channelPrefix + event; + this.subClient.subscribe(myChannel); const cb = (channel, message) => { if (channel === myChannel) { - if (event !== 'log') - Logger.debug(`接收事件:${channel} -> ${message}`, RedisSubPub.name) + if (event !== 'log') Logger.debug(`接收事件:${channel} -> ${message}`, RedisSubPub.name); - callback(JSON.parse(message)) + callback(JSON.parse(message)); } - } + }; - this.ctc.set(callback, cb) - this.subClient.on('message', cb) + this.ctc.set(callback, cb); + this.subClient.on('message', cb); } public async unsubscribe(event: string, callback: (data: any) => void) { - const channel = this.channelPrefix + event - this.subClient.unsubscribe(channel) - const cb = this.ctc.get(callback) + const channel = this.channelPrefix + event; + this.subClient.unsubscribe(channel); + const cb = this.ctc.get(callback); if (cb) { - this.subClient.off('message', cb) + this.subClient.off('message', cb); - this.ctc.delete(callback) + this.ctc.delete(callback); } } } diff --git a/src/shared/redis/redis.constant.ts b/src/shared/redis/redis.constant.ts index 9b82a3b..1111312 100644 --- a/src/shared/redis/redis.constant.ts +++ b/src/shared/redis/redis.constant.ts @@ -1 +1 @@ -export const REDIS_PUBSUB = Symbol('REDIS_PUBSUB') +export const REDIS_PUBSUB = Symbol('REDIS_PUBSUB'); diff --git a/src/shared/redis/redis.module.ts b/src/shared/redis/redis.module.ts index 5c19851..b227914 100644 --- a/src/shared/redis/redis.module.ts +++ b/src/shared/redis/redis.module.ts @@ -1,30 +1,30 @@ -import { RedisModule as NestRedisModule } from '@liaoliaots/nestjs-redis' -import { CacheModule } from '@nestjs/cache-manager' -import { Global, Module, Provider } from '@nestjs/common' -import { ConfigModule, ConfigService } from '@nestjs/config' +import { RedisModule as NestRedisModule } from '@liaoliaots/nestjs-redis'; +import { CacheModule } from '@nestjs/cache-manager'; +import { Global, Module, Provider } from '@nestjs/common'; +import { ConfigModule, ConfigService } from '@nestjs/config'; -import { redisStore } from 'cache-manager-ioredis-yet' -import { RedisOptions } from 'ioredis' +import { redisStore } from 'cache-manager-ioredis-yet'; +import { RedisOptions } from 'ioredis'; -import { ConfigKeyPaths, IRedisConfig } from '~/config' +import { ConfigKeyPaths, IRedisConfig } from '~/config'; -import { CacheService } from './cache.service' -import { RedisSubPub } from './redis-subpub' -import { REDIS_PUBSUB } from './redis.constant' -import { RedisPubSubService } from './subpub.service' +import { CacheService } from './cache.service'; +import { RedisSubPub } from './redis-subpub'; +import { REDIS_PUBSUB } from './redis.constant'; +import { RedisPubSubService } from './subpub.service'; const providers: Provider[] = [ CacheService, { provide: REDIS_PUBSUB, useFactory: (configService: ConfigService) => { - const redisOptions: RedisOptions = configService.get('redis') - return new RedisSubPub(redisOptions) + const redisOptions: RedisOptions = configService.get('redis'); + return new RedisSubPub(redisOptions); }, inject: [ConfigService], }, RedisPubSubService, -] +]; @Global() @Module({ @@ -33,14 +33,14 @@ const providers: Provider[] = [ CacheModule.registerAsync({ imports: [ConfigModule], useFactory: (configService: ConfigService) => { - const redisOptions: RedisOptions = configService.get('redis') + const redisOptions: RedisOptions = configService.get('redis'); return { isGlobal: true, store: redisStore, isCacheableValue: () => true, ...redisOptions, - } + }; }, inject: [ConfigService], }), diff --git a/src/shared/redis/subpub.service.ts b/src/shared/redis/subpub.service.ts index 2ba347f..fc9c409 100644 --- a/src/shared/redis/subpub.service.ts +++ b/src/shared/redis/subpub.service.ts @@ -1,21 +1,21 @@ -import { Inject, Injectable } from '@nestjs/common' +import { Inject, Injectable } from '@nestjs/common'; -import { RedisSubPub } from './redis-subpub' -import { REDIS_PUBSUB } from './redis.constant' +import { RedisSubPub } from './redis-subpub'; +import { REDIS_PUBSUB } from './redis.constant'; @Injectable() export class RedisPubSubService { constructor(@Inject(REDIS_PUBSUB) private readonly redisSubPub: RedisSubPub) {} public async publish(event: string, data: any) { - return this.redisSubPub.publish(event, data) + return this.redisSubPub.publish(event, data); } public async subscribe(event: string, callback: (data: any) => void) { - return this.redisSubPub.subscribe(event, callback) + return this.redisSubPub.subscribe(event, callback); } public async unsubscribe(event: string, callback: (data: any) => void) { - return this.redisSubPub.unsubscribe(event, callback) + return this.redisSubPub.unsubscribe(event, callback); } } diff --git a/src/shared/shared.module.ts b/src/shared/shared.module.ts index 59cf92e..f0f222f 100644 --- a/src/shared/shared.module.ts +++ b/src/shared/shared.module.ts @@ -1,16 +1,16 @@ -import { HttpModule } from '@nestjs/axios' -import { Global, Module } from '@nestjs/common' -import { EventEmitterModule } from '@nestjs/event-emitter' -import { ScheduleModule } from '@nestjs/schedule' -import { ThrottlerModule } from '@nestjs/throttler' +import { HttpModule } from '@nestjs/axios'; +import { Global, Module } from '@nestjs/common'; +import { EventEmitterModule } from '@nestjs/event-emitter'; +import { ScheduleModule } from '@nestjs/schedule'; +import { ThrottlerModule } from '@nestjs/throttler'; -import { isDev } from '~/global/env' +import { isDev } from '~/global/env'; -import { HelperModule } from './helper/helper.module' -import { LoggerModule } from './logger/logger.module' -import { MailerModule } from './mailer/mailer.module' +import { HelperModule } from './helper/helper.module'; +import { LoggerModule } from './logger/logger.module'; +import { MailerModule } from './mailer/mailer.module'; -import { RedisModule } from './redis/redis.module' +import { RedisModule } from './redis/redis.module'; @Global() @Module({ diff --git a/src/socket/base.gateway.ts b/src/socket/base.gateway.ts index a664892..8ce4e34 100644 --- a/src/socket/base.gateway.ts +++ b/src/socket/base.gateway.ts @@ -1,30 +1,22 @@ -import type { Socket } from 'socket.io' +import type { Socket } from 'socket.io'; -import { BusinessEvents } from './business-event.constant' +import { BusinessEvents } from './business-event.constant'; export abstract class BaseGateway { - public gatewayMessageFormat( - type: BusinessEvents, - message: any, - code?: number, - ) { + public gatewayMessageFormat(type: BusinessEvents, message: any, code?: number) { return { type, data: message, code, - } + }; } handleDisconnect(client: Socket) { - client.send( - this.gatewayMessageFormat(BusinessEvents.GATEWAY_CONNECT, 'WebSocket 断开'), - ) + client.send(this.gatewayMessageFormat(BusinessEvents.GATEWAY_CONNECT, 'WebSocket 断开')); } handleConnect(client: Socket) { - client.send( - this.gatewayMessageFormat(BusinessEvents.GATEWAY_CONNECT, 'WebSocket 已连接'), - ) + client.send(this.gatewayMessageFormat(BusinessEvents.GATEWAY_CONNECT, 'WebSocket 已连接')); } } diff --git a/src/socket/events/admin.gateway.ts b/src/socket/events/admin.gateway.ts index 60f930a..2b8c810 100644 --- a/src/socket/events/admin.gateway.ts +++ b/src/socket/events/admin.gateway.ts @@ -1,37 +1,38 @@ -import { JwtService } from '@nestjs/jwt' +import { JwtService } from '@nestjs/jwt'; import { GatewayMetadata, OnGatewayConnection, OnGatewayDisconnect, WebSocketGateway, WebSocketServer, -} from '@nestjs/websockets' +} from '@nestjs/websockets'; -import { Server } from 'socket.io' +import { Server } from 'socket.io'; -import { AuthService } from '~/modules/auth/auth.service' -import { CacheService } from '~/shared/redis/cache.service' +import { AuthService } from '~/modules/auth/auth.service'; +import { CacheService } from '~/shared/redis/cache.service'; -import { createAuthGateway } from '../shared/auth.gateway' +import { createAuthGateway } from '../shared/auth.gateway'; -const AuthGateway = createAuthGateway({ namespace: 'admin' }) +const AuthGateway = createAuthGateway({ namespace: 'admin' }); @WebSocketGateway({ namespace: 'admin' }) export class AdminEventsGateway extends AuthGateway - implements OnGatewayConnection, OnGatewayDisconnect { + implements OnGatewayConnection, OnGatewayDisconnect +{ constructor( protected readonly jwtService: JwtService, protected readonly authService: AuthService, - private readonly cacheService: CacheService, + private readonly cacheService: CacheService ) { - super(jwtService, authService, cacheService) + super(jwtService, authService, cacheService); } @WebSocketServer() - protected _server: Server + protected _server: Server; get server() { - return this._server + return this._server; } } diff --git a/src/socket/events/web.gateway.ts b/src/socket/events/web.gateway.ts index 94b83a3..7d46d32 100644 --- a/src/socket/events/web.gateway.ts +++ b/src/socket/events/web.gateway.ts @@ -1,36 +1,37 @@ -import { JwtService } from '@nestjs/jwt' +import { JwtService } from '@nestjs/jwt'; import { GatewayMetadata, OnGatewayConnection, OnGatewayDisconnect, WebSocketGateway, WebSocketServer, -} from '@nestjs/websockets' +} from '@nestjs/websockets'; -import { Server } from 'socket.io' +import { Server } from 'socket.io'; -import { TokenService } from '~/modules/auth/services/token.service' -import { CacheService } from '~/shared/redis/cache.service' +import { TokenService } from '~/modules/auth/services/token.service'; +import { CacheService } from '~/shared/redis/cache.service'; -import { createAuthGateway } from '../shared/auth.gateway' +import { createAuthGateway } from '../shared/auth.gateway'; -const AuthGateway = createAuthGateway({ namespace: 'web' }) +const AuthGateway = createAuthGateway({ namespace: 'web' }); @WebSocketGateway({ namespace: 'web' }) export class WebEventsGateway extends AuthGateway - implements OnGatewayConnection, OnGatewayDisconnect { + implements OnGatewayConnection, OnGatewayDisconnect +{ constructor( protected readonly jwtService: JwtService, protected readonly tokenService: TokenService, - private readonly cacheService: CacheService, + private readonly cacheService: CacheService ) { - super(jwtService, tokenService, cacheService) + super(jwtService, tokenService, cacheService); } @WebSocketServer() - protected _server: Server + protected _server: Server; get server() { - return this._server + return this._server; } } diff --git a/src/socket/shared/auth.gateway.ts b/src/socket/shared/auth.gateway.ts index 8285338..c18e59d 100644 --- a/src/socket/shared/auth.gateway.ts +++ b/src/socket/shared/auth.gateway.ts @@ -1,120 +1,114 @@ -import { } from '@nestjs/common' -import { OnEvent } from '@nestjs/event-emitter' -import { JwtService } from '@nestjs/jwt' -import type { - OnGatewayConnection, - OnGatewayDisconnect, -} from '@nestjs/websockets' -import { WebSocketServer } from '@nestjs/websockets' -import { Namespace } from 'socket.io' -import type { Socket } from 'socket.io' +import {} from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; +import { JwtService } from '@nestjs/jwt'; +import type { OnGatewayConnection, OnGatewayDisconnect } from '@nestjs/websockets'; +import { WebSocketServer } from '@nestjs/websockets'; +import { Namespace } from 'socket.io'; +import type { Socket } from 'socket.io'; -import { EventBusEvents } from '~/constants/event-bus.constant' +import { EventBusEvents } from '~/constants/event-bus.constant'; -import { TokenService } from '~/modules/auth/services/token.service' -import { CacheService } from '~/shared/redis/cache.service' +import { TokenService } from '~/modules/auth/services/token.service'; +import { CacheService } from '~/shared/redis/cache.service'; -import { BroadcastBaseGateway } from '../base.gateway' -import { BusinessEvents } from '../business-event.constant' +import { BroadcastBaseGateway } from '../base.gateway'; +import { BusinessEvents } from '../business-event.constant'; export interface AuthGatewayOptions { - namespace: string + namespace: string; } // eslint-disable-next-line ts/ban-ts-comment // @ts-expect-error -export interface IAuthGateway extends OnGatewayConnection, OnGatewayDisconnect, BroadcastBaseGateway {} +export interface IAuthGateway + extends OnGatewayConnection, + OnGatewayDisconnect, + BroadcastBaseGateway {} -export function createAuthGateway(options: AuthGatewayOptions): new (...args: any[]) => IAuthGateway { - const { namespace } = options +export function createAuthGateway( + options: AuthGatewayOptions +): new (...args: any[]) => IAuthGateway { + const { namespace } = options; class AuthGateway extends BroadcastBaseGateway implements IAuthGateway { constructor( protected readonly jwtService: JwtService, protected readonly tokenService: TokenService, - private readonly cacheService: CacheService, + private readonly cacheService: CacheService ) { - super() + super(); } @WebSocketServer() - protected namespace: Namespace + protected namespace: Namespace; async authFailed(client: Socket) { - client.send( - this.gatewayMessageFormat(BusinessEvents.AUTH_FAILED, '认证失败'), - ) - client.disconnect() + client.send(this.gatewayMessageFormat(BusinessEvents.AUTH_FAILED, '认证失败')); + client.disconnect(); } async authToken(token: string): Promise { - if (typeof token !== 'string') - return false + if (typeof token !== 'string') return false; const validJwt = async () => { try { - const ok = await this.jwtService.verify(token) + const ok = await this.jwtService.verify(token); - if (!ok) - return false - } - catch { - return false + if (!ok) return false; + } catch { + return false; } // is not crash, is verify - return true - } + return true; + }; - return await validJwt() + return await validJwt(); } async handleConnection(client: Socket) { - const token - = client.handshake.query.token - || client.handshake.headers.authorization - || client.handshake.headers.Authorization - if (!token) - return this.authFailed(client) + const token = + client.handshake.query.token || + client.handshake.headers.authorization || + client.handshake.headers.Authorization; + if (!token) return this.authFailed(client); - if (!(await this.authToken(token as string))) - return this.authFailed(client) + if (!(await this.authToken(token as string))) return this.authFailed(client); - super.handleConnect(client) + super.handleConnect(client); - const sid = client.id - this.tokenSocketIdMap.set(token.toString(), sid) + const sid = client.id; + this.tokenSocketIdMap.set(token.toString(), sid); } handleDisconnect(client: Socket) { - super.handleDisconnect(client) + super.handleDisconnect(client); } - tokenSocketIdMap = new Map() + tokenSocketIdMap = new Map(); @OnEvent(EventBusEvents.TokenExpired) handleTokenExpired(token: string) { // consola.debug(`token expired: ${token}`) - const server = this.namespace.server - const sid = this.tokenSocketIdMap.get(token) - if (!sid) - return false + const server = this.namespace.server; + const sid = this.tokenSocketIdMap.get(token); + if (!sid) return false; - const socket = server.of(`/${namespace}`).sockets.get(sid) + const socket = server.of(`/${namespace}`).sockets.get(sid); if (socket) { - socket.disconnect() - super.handleDisconnect(socket) - return true + socket.disconnect(); + super.handleDisconnect(socket); + return true; } - return false + return false; } override broadcast(event: BusinessEvents, data: any) { this.cacheService.emitter .of(`/${namespace}`) - .emit('message', this.gatewayMessageFormat(event, data)) + .emit('message', this.gatewayMessageFormat(event, data)); } } - return AuthGateway + return AuthGateway; } diff --git a/src/socket/socket.module.ts b/src/socket/socket.module.ts index a1a1de8..0affa35 100644 --- a/src/socket/socket.module.ts +++ b/src/socket/socket.module.ts @@ -1,12 +1,12 @@ -import { Module, Provider, forwardRef } from '@nestjs/common' +import { Module, Provider, forwardRef } from '@nestjs/common'; -import { AuthModule } from '../modules/auth/auth.module' -import { SystemModule } from '../modules/system/system.module' +import { AuthModule } from '../modules/auth/auth.module'; +import { SystemModule } from '../modules/system/system.module'; -import { AdminEventsGateway } from './events/admin.gateway' -import { WebEventsGateway } from './events/web.gateway' +import { AdminEventsGateway } from './events/admin.gateway'; +import { WebEventsGateway } from './events/web.gateway'; -const providers: Provider[] = [AdminEventsGateway, WebEventsGateway] +const providers: Provider[] = [AdminEventsGateway, WebEventsGateway]; @Module({ imports: [forwardRef(() => SystemModule), AuthModule], diff --git a/src/utils/captcha.util.ts b/src/utils/captcha.util.ts index 4f416bc..65ccf3d 100644 --- a/src/utils/captcha.util.ts +++ b/src/utils/captcha.util.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/src/utils/crypto.util.ts b/src/utils/crypto.util.ts index 83fd841..58dd902 100644 --- a/src/utils/crypto.util.ts +++ b/src/utils/crypto.util.ts @@ -1,30 +1,28 @@ -import CryptoJS from 'crypto-js' +import CryptoJS from 'crypto-js'; -const key = CryptoJS.enc.Utf8.parse('louisabcdefe9bc') -const iv = CryptoJS.enc.Utf8.parse('0123456789louis') +const key = CryptoJS.enc.Utf8.parse('louisabcdefe9bc'); +const iv = CryptoJS.enc.Utf8.parse('0123456789louis'); export function aesEncrypt(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 aesDecrypt(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(); } diff --git a/src/utils/date.util.ts b/src/utils/date.util.ts index 54a15d1..d275d1a 100644 --- a/src/utils/date.util.ts +++ b/src/utils/date.util.ts @@ -1,23 +1,23 @@ -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, + 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, + 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); } diff --git a/src/utils/file.util.ts b/src/utils/file.util.ts index 6d6ba48..35a63b6 100644 --- a/src/utils/file.util.ts +++ b/src/utils/file.util.ts @@ -1,9 +1,9 @@ -import fs from 'node:fs' -import path from 'node: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,76 +14,69 @@ 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 image - = 'bmp dib pcp dif wmf gif jpg tif eps psd cdr iff tga pcd mpt png jpeg' - if (image.includes(extName)) - return Type.IMAGE + 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'; + if (image.includes(extName)) return Type.IMAGE; - if (documents.includes(extName)) - return Type.TXT + if (documents.includes(extName)) return Type.TXT; - if (music.includes(extName)) - return Type.MUSIC + if (music.includes(extName)) return Type.MUSIC; - if (video.includes(extName)) - return Type.VIDEO + if (video.includes(extName)) return Type.VIDEO; - return Type.OTHER + return Type.OTHER; } export function getName(fileName: string) { - if (fileName.includes('.')) - return fileName.split('.')[0] + if (fileName.includes('.')) 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 `${Number.parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}` + return `${Number.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), () => { // console.log(error); - }) + }); } diff --git a/src/utils/index.ts b/src/utils/index.ts index b889238..598276a 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,11 +1,11 @@ -export * from './captcha.util' -export * from './crypto.util' -export * from './date.util' -export * from './file.util' -export * from './ip.util' -export * from './is.util' -export * from './list2tree.util' -export * from './permission.util' -export * from './redis.util' -export * from './schedule.util' -export * from './tool.util' +export * from './captcha.util'; +export * from './crypto.util'; +export * from './date.util'; +export * from './file.util'; +export * from './ip.util'; +export * from './is.util'; +export * from './list2tree.util'; +export * from './permission.util'; +export * from './redis.util'; +export * from './schedule.util'; +export * from './tool.util'; diff --git a/src/utils/ip.util.ts b/src/utils/ip.util.ts index 8c6ce06..60ce375 100644 --- a/src/utils/ip.util.ts +++ b/src/utils/ip.util.ts @@ -2,65 +2,56 @@ * @module utils/ip * @description IP utility functions */ -import type { IncomingMessage } from 'node:http' +import type { IncomingMessage } from 'node:http'; -import axios from 'axios' -import type { FastifyRequest } from 'fastify' +import axios from 'axios'; +import type { FastifyRequest } from 'fastify'; /* 判断IP是不是内网 */ function isLAN(ip: string) { - ip.toLowerCase() - if (ip === 'localhost') - return true - let a_ip = 0 - if (ip === '') - return false - const aNum = ip.split('.') - if (aNum.length !== 4) - return false - a_ip += Number.parseInt(aNum[0]) << 24 - a_ip += Number.parseInt(aNum[1]) << 16 - a_ip += Number.parseInt(aNum[2]) << 8 - a_ip += Number.parseInt(aNum[3]) << 0 - a_ip = (a_ip >> 16) & 0xFFFF + ip.toLowerCase(); + if (ip === 'localhost') return true; + let a_ip = 0; + if (ip === '') return false; + const aNum = ip.split('.'); + if (aNum.length !== 4) return false; + a_ip += Number.parseInt(aNum[0]) << 24; + a_ip += Number.parseInt(aNum[1]) << 16; + a_ip += Number.parseInt(aNum[2]) << 8; + a_ip += Number.parseInt(aNum[3]) << 0; + a_ip = (a_ip >> 16) & 0xffff; return ( - a_ip >> 8 === 0x7F - || a_ip >> 8 === 0xA - || a_ip === 0xC0A8 - || (a_ip >= 0xAC10 && a_ip <= 0xAC1F) - ) + a_ip >> 8 === 0x7f || a_ip >> 8 === 0xa || a_ip === 0xc0a8 || (a_ip >= 0xac10 && a_ip <= 0xac1f) + ); } export function getIp(request: FastifyRequest | IncomingMessage) { - const req = request as any + const req = request as any; - let ip: string - = request.headers['x-forwarded-for'] - || request.headers['X-Forwarded-For'] - || request.headers['X-Real-IP'] - || request.headers['x-real-ip'] - || req?.ip - || req?.raw?.connection?.remoteAddress - || req?.raw?.socket?.remoteAddress - || undefined - if (ip && ip.split(',').length > 0) - ip = ip.split(',')[0] + let ip: string = + request.headers['x-forwarded-for'] || + request.headers['X-Forwarded-For'] || + request.headers['X-Real-IP'] || + request.headers['x-real-ip'] || + req?.ip || + req?.raw?.connection?.remoteAddress || + req?.raw?.socket?.remoteAddress || + undefined; + if (ip && ip.split(',').length > 0) ip = ip.split(',')[0]; - return ip + return ip; } export async function getIpAddress(ip: string) { - if (isLAN(ip)) - return '内网IP' + if (isLAN(ip)) return '内网IP'; try { - let { data } = await axios.get( - `https://whois.pconline.com.cn/ipJson.jsp?ip=${ip}&json=true`, - { responseType: 'arraybuffer' }, - ) - data = new TextDecoder('gbk').decode(data) - data = JSON.parse(data) - return data.addr.trim().split(' ').at(0) + let { data } = await axios.get(`https://whois.pconline.com.cn/ipJson.jsp?ip=${ip}&json=true`, { + responseType: 'arraybuffer', + }); + data = new TextDecoder('gbk').decode(data); + data = JSON.parse(data); + return data.addr.trim().split(' ').at(0); } catch (error) { - return '第三方接口请求失败' + return '第三方接口请求失败'; } } diff --git a/src/utils/is.util.ts b/src/utils/is.util.ts index ce9e048..f3802cf 100644 --- a/src/utils/is.util.ts +++ b/src/utils/is.util.ts @@ -1,4 +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) + const reg = + /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/; + return reg.test(path); } diff --git a/src/utils/list2tree.util.ts b/src/utils/list2tree.util.ts index 663a9f4..d1999a3 100644 --- a/src/utils/list2tree.util.ts +++ b/src/utils/list2tree.util.ts @@ -1,27 +1,27 @@ 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, - parentId: number | null = null, + parentId: number | null = null ): TreeNode[] { return items .filter(item => item.parentId === parentId) - .map((item) => { - const children = list2Tree(items, item.id) + .map(item => { + const children = list2Tree(items, item.id); return { ...item, ...(children.length ? { children } : null), - } - }) + }; + }); } /** @@ -34,20 +34,19 @@ 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 }) + if (children.length) resTree.push({ ...data }); } - return resTree - } - return treeData.reduce(filterChildrenTree, []) + return resTree; + }; + return treeData.reduce(filterChildrenTree, []); } /** @@ -58,29 +57,25 @@ export function filterTree2List(treeData, key, value) { */ export function filterTree( treeData: TreeNode[], - predicate: (data: T) => boolean, + 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 + return treeData.filter(data => { + 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 function deleteEmptyChildren(arr: any) { - arr?.forEach((node) => { - if (node.children?.length === 0) - delete node.children - else - deleteEmptyChildren(node.children) - }) + arr?.forEach(node => { + if (node.children?.length === 0) delete node.children; + else deleteEmptyChildren(node.children); + }); } diff --git a/src/utils/permission.util.ts b/src/utils/permission.util.ts index 6aa34ce..6769122 100644 --- a/src/utils/permission.util.ts +++ b/src/utils/permission.util.ts @@ -1,8 +1,8 @@ -import { ForbiddenException } from '@nestjs/common' +import { ForbiddenException } from '@nestjs/common'; -import { envBoolean } from '~/global/env' -import { MenuEntity } from '~/modules/system/menu/menu.entity' -import { isExternal } from '~/utils/is.util' +import { envBoolean } from '~/global/env'; +import { MenuEntity } from '~/modules/system/menu/menu.entity'; +import { isExternal } from '~/utils/is.util'; function createRoute(menu: MenuEntity, _isRoot) { const commonMeta = { @@ -16,7 +16,7 @@ function createRoute(menu: MenuEntity, _isRoot) { activeMenu: menu.activeMenu, status: menu.status, keepAlive: menu.keepAlive, - } + }; if (isExternal(menu.path)) { return { @@ -25,7 +25,7 @@ function createRoute(menu: MenuEntity, _isRoot) { // component: 'IFrame', name: menu.name, meta: { ...commonMeta }, - } + }; } // 目录 @@ -36,7 +36,7 @@ function createRoute(menu: MenuEntity, _isRoot) { component: menu.component, name: menu.name, meta: { ...commonMeta }, - } + }; } return { @@ -47,112 +47,95 @@ function createRoute(menu: MenuEntity, _isRoot) { meta: { ...commonMeta, }, - } + }; } function filterAsyncRoutes(menus: MenuEntity[], parentRoute) { - const res = [] + const res = []; - menus.forEach((menu) => { + menus.forEach(menu => { if (menu.type === 2 || !menu.status) { // 如果是权限或禁用直接跳过 - return + return; } // 根级别菜单渲染 - let realRoute + let realRoute; if (!parentRoute && !menu.parentId && menu.type === 1) { // 根菜单 - realRoute = createRoute(menu, true) - } - else if (!parentRoute && !menu.parentId && menu.type === 0) { + realRoute = createRoute(menu, true); + } else if (!parentRoute && !menu.parentId && 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 - && parentRoute.id === menu.parentId - && menu.type === 1 - ) { + } else if (parentRoute && parentRoute.id === menu.parentId && menu.type === 1) { // 子菜单 - realRoute = createRoute(menu, false) - } - else if ( - parentRoute - && parentRoute.id === menu.parentId - && menu.type === 0 - ) { + realRoute = createRoute(menu, false); + } else if (parentRoute && parentRoute.id === menu.parentId && 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) - }) - return res + if (realRoute) res.push(realRoute); + }); + return res; } export function generatorRouters(menus: MenuEntity[]) { - return filterAsyncRoutes(menus, null) + return filterAsyncRoutes(menus, null); } // 获取所有菜单以及权限 function filterMenuToTable(menus: MenuEntity[], parentMenu) { - const res = [] - menus.forEach((menu) => { + const res = []; + menus.forEach(menu => { // 根级别菜单渲染 - let realMenu + let realMenu; if (!parentMenu && !menu.parentId && menu.type === 1) { // 根菜单,查找该跟菜单下子菜单,因为可能会包含权限 - const childMenu = filterMenuToTable(menus, menu) - realMenu = { ...menu } - realMenu.children = childMenu - } - else if (!parentMenu && !menu.parentId && menu.type === 0) { + const childMenu = filterMenuToTable(menus, menu); + realMenu = { ...menu }; + realMenu.children = childMenu; + } else if (!parentMenu && !menu.parentId && menu.type === 0) { // 根目录 - const childMenu = filterMenuToTable(menus, menu) - realMenu = { ...menu } - realMenu.children = childMenu - } - else if (parentMenu && parentMenu.id === menu.parentId && menu.type === 1) { + const childMenu = filterMenuToTable(menus, menu); + realMenu = { ...menu }; + realMenu.children = childMenu; + } else if (parentMenu && parentMenu.id === menu.parentId && menu.type === 1) { // 子菜单下继续找是否有子菜单 - const childMenu = filterMenuToTable(menus, menu) - realMenu = { ...menu } - realMenu.children = childMenu - } - else if (parentMenu && parentMenu.id === menu.parentId && menu.type === 0) { + const childMenu = filterMenuToTable(menus, menu); + realMenu = { ...menu }; + realMenu.children = childMenu; + } else if (parentMenu && parentMenu.id === menu.parentId && menu.type === 0) { // 如果还是目录,继续递归 - const childMenu = filterMenuToTable(menus, menu) - realMenu = { ...menu } - realMenu.children = childMenu - } - else if (parentMenu && parentMenu.id === menu.parentId && menu.type === 2) { - realMenu = { ...menu } + const childMenu = filterMenuToTable(menus, menu); + realMenu = { ...menu }; + realMenu.children = childMenu; + } else if (parentMenu && parentMenu.id === menu.parentId && menu.type === 2) { + 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: MenuEntity[]) { - return filterMenuToTable(menu, null) + return filterMenuToTable(menu, null); } /** 检测是否为演示环境, 如果为演示环境,则拒绝该操作 */ export function checkIsDemoMode() { - if (envBoolean('IS_DEMO')) - throw new ForbiddenException('演示模式下不允许操作') + if (envBoolean('IS_DEMO')) throw new ForbiddenException('演示模式下不允许操作'); } diff --git a/src/utils/redis.util.ts b/src/utils/redis.util.ts index c09380c..f18dad5 100644 --- a/src/utils/redis.util.ts +++ b/src/utils/redis.util.ts @@ -1,10 +1,11 @@ -import type { RedisKeys } from '~/constants/cache.constant' +import type { RedisKeys } from '~/constants/cache.constant'; -type Prefix = 'm-shop' -const prefix = 'm-shop' +type Prefix = 'm-shop'; +const prefix = 'm-shop'; -export function getRedisKey(key: T, ...concatKeys: string[]): `${Prefix}:${T}${string | ''}` { - return `${prefix}:${key}${ - concatKeys && concatKeys.length ? `:${concatKeys.join('_')}` : '' - }` +export function getRedisKey( + key: T, + ...concatKeys: string[] +): `${Prefix}:${T}${string | ''}` { + return `${prefix}:${key}${concatKeys && concatKeys.length ? `:${concatKeys.join('_')}` : ''}`; } diff --git a/src/utils/schedule.util.ts b/src/utils/schedule.util.ts index 184fede..052fc80 100644 --- a/src/utils/schedule.util.ts +++ b/src/utils/schedule.util.ts @@ -1,63 +1,60 @@ -const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)) +const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); export function scheduleMicrotask(callback: () => void) { - sleep(0).then(callback) + sleep(0).then(callback); } -type NotifyCallback = () => void +type NotifyCallback = () => void; -type NotifyFunction = (callback: () => void) => void +type NotifyFunction = (callback: () => void) => void; -type BatchNotifyFunction = (callback: () => void) => void +type BatchNotifyFunction = (callback: () => void) => void; export function createNotifyManager() { - let queue: NotifyCallback[] = [] - let transactions = 0 - let notifyFn: NotifyFunction = (callback) => { - callback() - } + let queue: NotifyCallback[] = []; + let transactions = 0; + let notifyFn: NotifyFunction = callback => { + callback(); + }; let batchNotifyFn: BatchNotifyFunction = (callback: () => void) => { - callback() - } + callback(); + }; const flush = (): void => { - const originalQueue = queue - queue = [] + const originalQueue = queue; + queue = []; if (originalQueue.length) { scheduleMicrotask(() => { batchNotifyFn(() => { - originalQueue.forEach((callback) => { - notifyFn(callback) - }) - }) - }) + originalQueue.forEach(callback => { + notifyFn(callback); + }); + }); + }); } - } + }; const batch = (callback: () => T): T => { - let result - transactions++ + let result; + transactions++; try { - result = callback() + result = callback(); + } finally { + transactions--; + if (!transactions) flush(); } - finally { - transactions-- - if (!transactions) - flush() - } - return result - } + return result; + }; const schedule = (callback: NotifyCallback): void => { if (transactions) { - queue.push(callback) - } - else { + queue.push(callback); + } else { scheduleMicrotask(() => { - notifyFn(callback) - }) + notifyFn(callback); + }); } - } + }; /** * All calls to the wrapped function will be batched. @@ -65,26 +62,26 @@ export function createNotifyManager() { const batchCalls = (callback: T): T => { return ((...args: any[]) => { schedule(() => { - callback(...args) - }) - }) as any - } + callback(...args); + }); + }) as any; + }; /** * Use this method to set a custom notify function. * This can be used to for example wrap notifications with `React.act` while running tests. */ const setNotifyFunction = (fn: NotifyFunction) => { - notifyFn = fn - } + notifyFn = fn; + }; /** * Use this method to set a custom function to batch notifications together into a single tick. * By default React Query will use the batch function provided by ReactDOM or React Native. */ const setBatchNotifyFunction = (fn: BatchNotifyFunction) => { - batchNotifyFn = fn - } + batchNotifyFn = fn; + }; return { batch, @@ -92,8 +89,8 @@ export function createNotifyManager() { schedule, setNotifyFunction, setBatchNotifyFunction, - } as const + } as const; } // SINGLETON -export const scheduleManager = createNotifyManager() +export const scheduleManager = createNotifyManager(); diff --git a/src/utils/tool.util.ts b/src/utils/tool.util.ts index 9982cf5..4bd86d8 100644 --- a/src/utils/tool.util.ts +++ b/src/utils/tool.util.ts @@ -1,20 +1,19 @@ -import { customAlphabet, nanoid } from 'nanoid' +import { customAlphabet, nanoid } from 'nanoid'; -import { md5 } from './crypto.util' +import { md5 } from './crypto.util'; export function getAvatar(mail: string | undefined) { - if (!mail) - return '' + if (!mail) return ''; - return `https://cravatar.cn/avatar/${md5(mail)}?d=retro` + return `https://cravatar.cn/avatar/${md5(mail)}?d=retro`; } export function generateUUID(size: number = 21): string { - return nanoid(size) + return nanoid(size); } export function generateShortUUID(): string { - return nanoid(10) + return nanoid(10); } /** @@ -22,10 +21,10 @@ export function generateShortUUID(): string { */ export function generateRandomValue( length: number, - placeholder = '1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM', + placeholder = '1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM' ): string { - const customNanoid = customAlphabet(placeholder, length) - return customNanoid() + const customNanoid = customAlphabet(placeholder, length); + return customNanoid(); } /** @@ -33,28 +32,24 @@ export function generateRandomValue( */ export function randomValue( size = 16, - dict = 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict', + 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; } export const hashString = function (str, seed = 0) { - let h1 = 0xDEADBEEF ^ seed - let h2 = 0x41C6CE57 ^ seed + let h1 = 0xdeadbeef ^ seed; + let h2 = 0x41c6ce57 ^ seed; for (let i = 0, ch; i < str.length; i++) { - ch = str.charCodeAt(i) - h1 = Math.imul(h1 ^ ch, 2654435761) - h2 = Math.imul(h2 ^ ch, 1597334677) + ch = str.charCodeAt(i); + h1 = Math.imul(h1 ^ ch, 2654435761); + h2 = Math.imul(h2 ^ ch, 1597334677); } - h1 - = Math.imul(h1 ^ (h1 >>> 16), 2246822507) - ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909) - h2 - = Math.imul(h2 ^ (h2 >>> 16), 2246822507) - ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909) - return 4294967296 * (2097151 & h2) + (h1 >>> 0) -} + h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909); + h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909); + return 4294967296 * (2097151 & h2) + (h1 >>> 0); +};