From 6adadc5dc361c0da62f638ef64c3906321f14036 Mon Sep 17 00:00:00 2001 From: COOL Date: Thu, 16 Jan 2025 21:29:59 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=BA=86=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E4=B8=8D=E4=BE=9D=E8=B5=96redis=EF=BC=8Ccluster=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=E4=B8=8B=E5=8F=AF=E7=94=A8=E7=9A=84=E6=9C=AC=E5=9C=B0?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bootstrap.js | 1 + package.json | 44 +- pnpm-lock.yaml | 1232 +++++++++++++++--- src/comm/path.ts | 58 + src/config/config.default.ts | 15 +- src/config/config.local.ts | 22 +- src/config/config.prod.ts | 28 +- src/configuration.ts | 4 +- src/entities.ts | 45 +- src/index.ts | 107 ++ src/modules/base/db.json | 1 - src/modules/demo/config.ts | 19 + src/modules/demo/controller/open/plugin.ts | 25 + src/modules/plugin/entity/info.ts | 12 + src/modules/plugin/hooks/upload/index.ts | 16 +- src/modules/plugin/service/info.ts | 93 +- src/modules/task/config.ts | 23 + src/modules/task/controller/admin/info.ts | 59 + src/modules/task/db.json | 40 + src/modules/task/entity/info.ts | 62 + src/modules/task/entity/log.ts | 18 + src/modules/task/event/app.ts | 17 + src/modules/task/middleware/task.ts | 38 + src/modules/task/queue/task.ts | 30 + src/modules/task/service/bull.ts | 339 +++++ src/modules/task/service/demo.ts | 19 + src/modules/task/service/info.ts | 153 +++ src/modules/task/service/local.ts | 336 +++++ src/modules/user/config.ts | 34 + src/modules/user/controller/admin/address.ts | 13 + src/modules/user/controller/admin/info.ts | 15 + src/modules/user/controller/app/address.ts | 39 + src/modules/user/controller/app/comm.ts | 25 + src/modules/user/controller/app/info.ts | 65 + src/modules/user/controller/app/login.ts | 106 ++ src/modules/user/entity/address.ts | 34 + src/modules/user/entity/info.ts | 34 + src/modules/user/entity/wx.ts | 40 + src/modules/user/event/app.ts | 56 + src/modules/user/middleware/app.ts | 96 ++ src/modules/user/service/address.ts | 63 + src/modules/user/service/info.ts | 124 ++ src/modules/user/service/login.ts | 307 +++++ src/modules/user/service/sms.ts | 79 ++ src/modules/user/service/wx.ts | 281 ++++ typings/feishu.d.ts | 96 ++ typings/plugin.d.ts | 2 + 47 files changed, 4065 insertions(+), 300 deletions(-) create mode 100644 src/comm/path.ts create mode 100644 src/index.ts create mode 100644 src/modules/demo/config.ts create mode 100644 src/modules/demo/controller/open/plugin.ts create mode 100644 src/modules/task/config.ts create mode 100644 src/modules/task/controller/admin/info.ts create mode 100644 src/modules/task/db.json create mode 100644 src/modules/task/entity/info.ts create mode 100644 src/modules/task/entity/log.ts create mode 100644 src/modules/task/event/app.ts create mode 100644 src/modules/task/middleware/task.ts create mode 100644 src/modules/task/queue/task.ts create mode 100644 src/modules/task/service/bull.ts create mode 100644 src/modules/task/service/demo.ts create mode 100644 src/modules/task/service/info.ts create mode 100644 src/modules/task/service/local.ts create mode 100644 src/modules/user/config.ts create mode 100644 src/modules/user/controller/admin/address.ts create mode 100644 src/modules/user/controller/admin/info.ts create mode 100644 src/modules/user/controller/app/address.ts create mode 100644 src/modules/user/controller/app/comm.ts create mode 100644 src/modules/user/controller/app/info.ts create mode 100644 src/modules/user/controller/app/login.ts create mode 100644 src/modules/user/entity/address.ts create mode 100644 src/modules/user/entity/info.ts create mode 100644 src/modules/user/entity/wx.ts create mode 100644 src/modules/user/event/app.ts create mode 100644 src/modules/user/middleware/app.ts create mode 100644 src/modules/user/service/address.ts create mode 100644 src/modules/user/service/info.ts create mode 100644 src/modules/user/service/login.ts create mode 100644 src/modules/user/service/sms.ts create mode 100644 src/modules/user/service/wx.ts create mode 100644 typings/feishu.d.ts diff --git a/bootstrap.js b/bootstrap.js index 8601e1f..6444271 100644 --- a/bootstrap.js +++ b/bootstrap.js @@ -1,3 +1,4 @@ +process.env.NODE_ENV = 'local'; const { Bootstrap } = require('@midwayjs/bootstrap'); // 显式以组件方式引入用户代码 diff --git a/package.json b/package.json index fba36bf..c51a675 100644 --- a/package.json +++ b/package.json @@ -5,26 +5,30 @@ "private": true, "dependencies": { "@cool-midway/core": "file:/Users/ap/Documents/src/admin/midway-packages/core", - "@midwayjs/bootstrap": "^3.19.3", - "@midwayjs/cache-manager": "^3.19.3", - "@midwayjs/core": "^3.19.0", - "@midwayjs/cron": "^3.19.2", - "@midwayjs/cross-domain": "^3.19.3", - "@midwayjs/info": "^3.19.2", - "@midwayjs/koa": "^3.19.2", + "@cool-midway/task": "file:/Users/ap/Documents/src/admin/midway-packages/task", + "@midwayjs/bootstrap": "^3.20.0", + "@midwayjs/cache-manager": "^3.20.0", + "@midwayjs/core": "^3.20.0", + "@midwayjs/cron": "^3.20.0", + "@midwayjs/cross-domain": "^3.20.0", + "@midwayjs/info": "^3.20.0", + "@midwayjs/koa": "^3.20.0", "@midwayjs/logger": "^3.4.2", - "@midwayjs/static-file": "^3.19.3", - "@midwayjs/typeorm": "^3.19.2", - "@midwayjs/upload": "^3.19.3", - "@midwayjs/validate": "^3.19.2", - "@midwayjs/view-ejs": "^3.19.2", + "@midwayjs/static-file": "^3.20.0", + "@midwayjs/typeorm": "^3.20.0", + "@midwayjs/upload": "^3.20.0", + "@midwayjs/validate": "^3.20.0", + "@midwayjs/view-ejs": "^3.20.0", + "adm-zip": "^0.5.16", "axios": "^1.7.9", + "cron": "^3.5.0", "jsonwebtoken": "^9.0.2", "lodash": "^4.17.21", "md5": "^2.3.0", "moment": "^2.30.1", "mysql2": "^3.12.0", - "sharp": "0.32.6", + "sharp": "0.33.5", + "sqlite3": "^5.1.7", "svg-captcha": "^1.4.0", "typeorm": "^0.3.20", "uuid": "^11.0.5", @@ -32,7 +36,7 @@ }, "devDependencies": { "@midwayjs/bundle-helper": "^1.3.0", - "@midwayjs/mock": "^3.19.2", + "@midwayjs/mock": "^3.20.0", "@types/jest": "^29.5.14", "@types/node": "22", "cross-env": "^7.0.3", @@ -40,7 +44,7 @@ "mwts": "^1.3.0", "mwtsc": "^1.15.1", "pkg": "^5.8.1", - "rimraf": "^5.0.5", + "rimraf": "^6.0.1", "ts-jest": "^29.2.5", "typescript": "~5.7.3" }, @@ -49,7 +53,7 @@ }, "scripts": { "start": "NODE_ENV=production node ./bootstrap.js", - "dev": "rimraf src/index.ts && cool check entity --clear && cross-env NODE_ENV=local mwtsc --cleanOutDir --watch --run @midwayjs/mock/app.js", + "dev": "rimraf src/index.ts && cool check entity && cross-env NODE_ENV=local mwtsc --cleanOutDir --watch --run @midwayjs/mock/app.js", "test": "cross-env NODE_ENV=unittest jest", "cov": "jest --coverage", "lint": "mwts check", @@ -63,15 +67,17 @@ }, "bin": "./bootstrap.js", "pkg": { - "scripts": "dist/**/*", + "scripts": [ + "dist/**/*", + "node_modules/axios/dist/node/*" + ], "assets": [ "public/**/*", "typings/**/*", "cool/**/*" ], "targets": [ - "node18-macos-x64", - "node18-win-x64" + "node18-macos-x64" ], "outputPath": "build" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7b36600..244fb14 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,48 +11,57 @@ importers: '@cool-midway/core': specifier: file:/Users/ap/Documents/src/admin/midway-packages/core version: file:../midway-packages/core + '@cool-midway/task': + specifier: file:/Users/ap/Documents/src/admin/midway-packages/task + version: file:../midway-packages/task '@midwayjs/bootstrap': - specifier: ^3.19.3 - version: 3.19.3 + specifier: ^3.20.0 + version: 3.20.0 '@midwayjs/cache-manager': - specifier: ^3.19.3 - version: 3.19.3 + specifier: ^3.20.0 + version: 3.20.0 '@midwayjs/core': - specifier: ^3.19.0 - version: 3.19.0 + specifier: ^3.20.0 + version: 3.20.0 '@midwayjs/cron': - specifier: ^3.19.2 - version: 3.19.2 + specifier: ^3.20.0 + version: 3.20.0 '@midwayjs/cross-domain': - specifier: ^3.19.3 - version: 3.19.3 + specifier: ^3.20.0 + version: 3.20.0 '@midwayjs/info': - specifier: ^3.19.2 - version: 3.19.2 + specifier: ^3.20.0 + version: 3.20.0 '@midwayjs/koa': - specifier: ^3.19.2 - version: 3.19.2 + specifier: ^3.20.0 + version: 3.20.0 '@midwayjs/logger': specifier: ^3.4.2 version: 3.4.2 '@midwayjs/static-file': - specifier: ^3.19.3 - version: 3.19.3 + specifier: ^3.20.0 + version: 3.20.0 '@midwayjs/typeorm': - specifier: ^3.19.2 - version: 3.19.2 + specifier: ^3.20.0 + version: 3.20.0 '@midwayjs/upload': - specifier: ^3.19.3 - version: 3.19.3 + specifier: ^3.20.0 + version: 3.20.0 '@midwayjs/validate': - specifier: ^3.19.2 - version: 3.19.2 + specifier: ^3.20.0 + version: 3.20.0 '@midwayjs/view-ejs': - specifier: ^3.19.2 - version: 3.19.2 + specifier: ^3.20.0 + version: 3.20.0 + adm-zip: + specifier: ^0.5.16 + version: 0.5.16 axios: specifier: ^1.7.9 version: 1.7.9 + cron: + specifier: ^3.5.0 + version: 3.5.0 jsonwebtoken: specifier: ^9.0.2 version: 9.0.2 @@ -69,14 +78,17 @@ importers: specifier: ^3.12.0 version: 3.12.0 sharp: - specifier: 0.32.6 - version: 0.32.6 + specifier: 0.33.5 + version: 0.33.5 + sqlite3: + specifier: ^5.1.7 + version: 5.1.7 svg-captcha: specifier: ^1.4.0 version: 1.4.0 typeorm: specifier: ^0.3.20 - version: 0.3.20(mysql2@3.12.0) + version: 0.3.20(ioredis@5.4.2)(mysql2@3.12.0)(sqlite3@5.1.7) uuid: specifier: ^11.0.5 version: 11.0.5 @@ -88,8 +100,8 @@ importers: specifier: ^1.3.0 version: 1.3.0 '@midwayjs/mock': - specifier: ^3.19.2 - version: 3.19.2 + specifier: ^3.20.0 + version: 3.20.0 '@types/jest': specifier: ^29.5.14 version: 29.5.14 @@ -110,10 +122,10 @@ importers: version: 1.15.1 pkg: specifier: ^5.8.1 - version: 5.8.1 + version: 5.8.1(encoding@0.1.13) rimraf: - specifier: ^5.0.5 - version: 5.0.10 + specifier: ^6.0.1 + version: 6.0.1 ts-jest: specifier: ^29.2.5 version: 29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@22.10.5))(typescript@5.7.3) @@ -320,6 +332,13 @@ packages: resolution: {directory: ../midway-packages/core, type: directory} hasBin: true + '@cool-midway/task@file:../midway-packages/task': + resolution: {directory: ../midway-packages/task, type: directory} + hasBin: true + + '@emnapi/runtime@1.3.1': + resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==} + '@eslint-community/eslint-utils@4.4.1': resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -334,6 +353,9 @@ packages: resolution: {integrity: sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==} engines: {node: ^10.12.0 || >=12.0.0} + '@gar/promisify@1.1.3': + resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} + '@hapi/bourne@3.0.0': resolution: {integrity: sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==} @@ -352,6 +374,126 @@ packages: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} deprecated: Use @eslint/object-schema instead + '@img/sharp-darwin-arm64@0.33.5': + resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.33.5': + resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.0.4': + resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.0.4': + resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.0.4': + resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.0.5': + resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-s390x@1.0.4': + resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.0.4': + resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linux-arm64@0.33.5': + resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.33.5': + resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-s390x@0.33.5': + resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.33.5': + resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linuxmusl-arm64@0.33.5': + resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.33.5': + resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-wasm32@0.33.5': + resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-ia32@0.33.5': + resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.33.5': + resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@ioredis/commands@1.2.0': + resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==} + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -464,12 +606,12 @@ packages: resolution: {integrity: sha512-sYcHglGKTxGF+hQ6x67xDfkE9o+NhVlRHBqq6gLywaMc6CojK/5vFZByphdonKinYlMLkEkacm+HEse9HzwgTA==} engines: {node: '>= 12'} - '@midwayjs/async-hooks-context-manager@3.19.0': - resolution: {integrity: sha512-jPVBR2wweN8F1bBx6gZhm70+drA0bo3TQBRsfTCIt0MJv3lTzsn/agRi1YgcQ+BXQnLDaGEUiTVTV4J2Ak/Rqw==} + '@midwayjs/async-hooks-context-manager@3.20.0': + resolution: {integrity: sha512-uHhUs5jlbBGC6cG3H+SICjY/VKBTzXNlIfEEycQ9kbUVfF+y5gI8MyF/vhtmP/Y5sdqHoQI7BFx1XATqt85rxg==} engines: {node: '>=12.17.0'} - '@midwayjs/bootstrap@3.19.3': - resolution: {integrity: sha512-uyuR7jv7n4jNMJQBhBQVGGZj2f2Tnh9BYmAbqC4GsJ2qsTrxoG6M70JQwT7wOdNOXY4k+mnD6ux/QyeQy6vyXA==} + '@midwayjs/bootstrap@3.20.0': + resolution: {integrity: sha512-zRE39XRI4GmgUAbd4+3WL4h4g92MPCqbQ6yWb0dqjv0CTGNNtUGiUiK5KAE84AIUfUtW5womVwh9YgZEMD3NyQ==} engines: {node: '>=12.11.0'} '@midwayjs/bundle-helper@1.3.0': @@ -477,8 +619,8 @@ packages: engines: {node: '>=12.0.0'} hasBin: true - '@midwayjs/cache-manager@3.19.3': - resolution: {integrity: sha512-aksDyxpFOm9fRCdi3efA9v3NsXlMUm2YxaO/00ZQRAM3pDcNom55WiV3MKsZUDugvYa6+HsYAmL2wJR8PJcu/Q==} + '@midwayjs/cache-manager@3.20.0': + resolution: {integrity: sha512-dbEidbEMwbgCGYjbQt+EyFhqRXgOfkj80JrbCSPki33EhAxCcNjg8popJCe25iVKMU4JlKQrLm7mcJyk3R4z2g==} engines: {node: '>=12'} '@midwayjs/cache@3.14.0': @@ -489,16 +631,16 @@ packages: resolution: {integrity: sha512-gdfAXk3+uL+9qcNo9lNnUFJCUGXXxCitBjyz9QJR08dcZ11mqTbxVNXhea525e3ZwKREy6f4PHAWofFwlPOZzQ==} engines: {node: '>=10'} - '@midwayjs/core@3.19.0': - resolution: {integrity: sha512-qFoaFeN8c0UcfJKwmXoFmFxhMVMIT+MwUcTt1A9r/7lGmuqTukUICv5dxhTFffIr6YJHhSMOXEK5u9FV/NxGGA==} + '@midwayjs/core@3.20.0': + resolution: {integrity: sha512-Y7FrdaWgIUdyl0kVub+YhAIimgZugOPYw3ud/aUcB813ZN17IbGnmSFFyssO5xcoH8c8D0PRWVrT0jUdWUdS9A==} engines: {node: '>=12'} - '@midwayjs/cron@3.19.2': - resolution: {integrity: sha512-z6W80bs/MD6RQksDiruD+JqDXXO94CL609PseneAGFOrMdhlHHoGUhCDkE9FJ+QbiKItzpuqUZmmVDA3QRwbeA==} + '@midwayjs/cron@3.20.0': + resolution: {integrity: sha512-Z57Qurq1JbirEWZRalntgSRXsa1Z2N8Z1Nn/5dOATTJkq4rEaMMuSe04j/3+Pedh2T9KepNlIE+uLjlBcgT0Xw==} engines: {node: '>=12'} - '@midwayjs/cross-domain@3.19.3': - resolution: {integrity: sha512-NL5SkiUwJJ+3/MrACvl3zBZ6lMNOFqrGJKVUn5V9qIWmZ+oZz6Rhoi7x/tqrgR686SNH71HDIoYkj9Gey5yTFQ==} + '@midwayjs/cross-domain@3.20.0': + resolution: {integrity: sha512-pBLVuWhDFE+ouMnDa+o8voShBo0m1+1m9vrGJoI6rmMdwWmsXzDHrb6IYdmasir8BrQ+7j2n8ZjuTAOtnqIV6A==} engines: {node: '>=12'} '@midwayjs/event-bus@1.11.1': @@ -508,55 +650,85 @@ packages: '@midwayjs/glob@1.1.1': resolution: {integrity: sha512-xoNU+JdCxE214KQrB0qgs88+Da7KYVICeuTL9VeKwaxZXj6a/PbpmEaH4+5BHLvZRRe78tHGR19+nKZrmMHBJg==} - '@midwayjs/i18n@3.19.2': - resolution: {integrity: sha512-mOv1R3ZIsX0iDcHYTAA1HQisHqgMsxnCp3OxC7YGaEcQgJVyECbqqPDl9n4IQG8ntP5WshXVZAXn8MTbca0OVA==} + '@midwayjs/i18n@3.20.0': + resolution: {integrity: sha512-ssG0G2jf/xIe0MqLcP+y+MD33T2s0+Bq7RC9fwciUxrFeAdhkF6LTgeBQ9nS0FnFHZD4tt40oXMswZrnmYkghA==} engines: {node: '>=12'} - '@midwayjs/info@3.19.2': - resolution: {integrity: sha512-tfWMemhc0+oBMFP9lvmVKiYjn6TB0iBcvD2evB0UDaqx7eJkCO9SQ5JfY6TJzYQvA/X24S2WhA1O81lLSa1dhQ==} + '@midwayjs/info@3.20.0': + resolution: {integrity: sha512-fB63R3cEQ9FImzV07DzESVTr7tVCG49nFr9DQw8rzcfRnshGxVrU48991V6WQP6nar+mQFDzldUk7FqvIOzXAQ==} engines: {node: '>=12'} - '@midwayjs/koa@3.19.2': - resolution: {integrity: sha512-F25hMArjUoGlfKddbbH3VqvjPkxp8tdN6Jv/hKr4Hs1sMWuJd3IjBOkdKP9EhYL6adza9eJuBkX8QbzkKHz3Jw==} + '@midwayjs/koa@3.20.0': + resolution: {integrity: sha512-1G24TqwfxQxRculIyVNZDocoFSREL7BOK9d9B2ufGduPIVRJ4g7LL1ggRDuSWKyjysX7SzeIhVqEjN8zHqfLMA==} engines: {node: '>=12'} '@midwayjs/logger@3.4.2': resolution: {integrity: sha512-BxSdSMog4jxeqpHwgauuKZT6iAiu/Vr1HDjO9l81iqU+l8c9veAJVeLT08nTqKdjUiQXnxTaW6nYlvnzF2HdMA==} engines: {node: '>=10'} - '@midwayjs/mock@3.19.2': - resolution: {integrity: sha512-gwgMoB3u2JhS59AmVM8uA9rlsQXZeYBJzzGGMsmZ/bz0qaFinyPrUIPPQik4NMfa+v7i3hTPf1U9yxWaLSzZHA==} + '@midwayjs/mock@3.20.0': + resolution: {integrity: sha512-ZZASgdhHWkqyXuuNxMozKhCTDY6/wCCiogHL+Y/YGW50VdMfs2DXMzCH7Y3eihDF8IcZa7V0z48S9dA/aOB2Eg==} engines: {node: '>=12'} - '@midwayjs/session@3.19.2': - resolution: {integrity: sha512-SRZWFrEw5BPDTIRjOyh0/YoAiafQBbeR+by62kl/s9noy47UlJIRzGcXORbUHpKIbv+7u9jCnZyniXpNNJ53EA==} + '@midwayjs/session@3.20.0': + resolution: {integrity: sha512-W5y6Q6JYx7y6gqfztOKjF/CxBtd298R2/sd/DpH0U+JwJECnG6+T528jbP2s6TPYUOjx12nVvRMHQ9w+VtM3qw==} engines: {node: '>=12'} - '@midwayjs/static-file@3.19.3': - resolution: {integrity: sha512-m2Gr2QIlwMTsb2pXUOMqT5ATCFITReX5U5yZBHrS4yEPg1MpE+ucyFU4DTXtee3IwOLJCgfpMuzVw+wqBXNW2w==} + '@midwayjs/static-file@3.20.0': + resolution: {integrity: sha512-+AjW8iINwyHZUh4TO4zEZs6wQa4zxKn0cyiqpJFvlLqV2+hauElaDnmX6BaezMtMHBcUs59q9BYRdMMyBhMDYg==} engines: {node: '>=12'} - '@midwayjs/typeorm@3.19.2': - resolution: {integrity: sha512-dJnkEZsGr7wON0HkKys51a4iHPzP1gyjyPcbcgqLJdfeJ3iCTOuQ7dDsBz0TTU8V7iYMTgzHcSgtkkl5j9+4jA==} + '@midwayjs/typeorm@3.20.0': + resolution: {integrity: sha512-PaqsALHVmw877zGuu16LjGeFY/5AdVk01hqxkk6NfvW7KJ3iU18vE+WVk9Wxeh/2ue/LK+itCgVX5aam0kle0A==} engines: {node: '>=12'} hasBin: true - '@midwayjs/upload@3.19.3': - resolution: {integrity: sha512-DMfO4GuydaNgqkCCZAigwDVP0n2K3dn+qeGbvGBhBA+f89zbo4mWNT7iGp/9kA5KuM3A0RDlppCWAvc77CpAxg==} + '@midwayjs/upload@3.20.0': + resolution: {integrity: sha512-rp4JDlpUfU/kDByGgsyq4XbxvFDdFL0yUlO01wApa9geeRShlyHwkYl3dqFCHygnd+GshwPc0/6Gld1a0tqNHw==} engines: {node: '>=12'} - '@midwayjs/validate@3.19.2': - resolution: {integrity: sha512-hG/SF8J2o1cvDwErYAXO1oC4a4cvJL+CTbOCaxjRCJRbEamkalCPE82nCQsjkePbcBec15/aSWQzL/fXb2uXKw==} + '@midwayjs/validate@3.20.0': + resolution: {integrity: sha512-LCMna/wAz4LDRKyMQh8Uoh+2W4qhdNJ4frAX5gzrxIUbCN5uTbBpmqqYSLA/j24ItSye4htSdQ7r+H9WM+LUIw==} engines: {node: '>=12'} - '@midwayjs/view-ejs@3.19.2': - resolution: {integrity: sha512-sVAXfAO8IJ9uUdPhPZia6eQHmKW2/871gfl6sPyc1enz/Cqizz0YKbe/UEYkLXXms/qGg2PtZvxQ7ehXHqw0zQ==} + '@midwayjs/view-ejs@3.20.0': + resolution: {integrity: sha512-HyFAeE6UqmmY7mWDeMsTKXYOARBIAL4Ce9l28hjJudJxTokf514l9OeRpPyWCb6hwMJf/AKgqyYZBcGFLiXhNA==} engines: {node: '>=12'} - '@midwayjs/view@3.19.2': - resolution: {integrity: sha512-rmhVUO0s1l48jwlqaYqMyn4oJMsvMV9sL1S6gVWBa05hUH9ByfZuw1NpfFCeDNz/YAt4HOeApm7Rgg+5IOs8pQ==} + '@midwayjs/view@3.20.0': + resolution: {integrity: sha512-rb8hrrfjnA0t7HFoiVb9JqCKzB/OULDbVRBy2gDy2EcnN1da7RtX7m7VU8hCAkqC7ClGDOOhghaYigmp/iVn6A==} engines: {node: '>=12'} + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': + resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==} + cpu: [arm64] + os: [darwin] + + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': + resolution: {integrity: sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==} + cpu: [x64] + os: [darwin] + + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': + resolution: {integrity: sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==} + cpu: [arm64] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': + resolution: {integrity: sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==} + cpu: [arm] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': + resolution: {integrity: sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==} + cpu: [x64] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': + resolution: {integrity: sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==} + cpu: [x64] + os: [win32] + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -569,6 +741,14 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@npmcli/fs@1.1.1': + resolution: {integrity: sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==} + + '@npmcli/move-file@1.1.2': + resolution: {integrity: sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==} + engines: {node: '>=10'} + deprecated: This functionality has been moved to @npmcli/fs + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -623,6 +803,10 @@ packages: '@tokenizer/token@0.3.0': resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + '@tootallnate/once@1.1.2': + resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==} + engines: {node: '>= 6'} + '@tootallnate/quickjs-emscripten@0.23.0': resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} @@ -701,6 +885,9 @@ packages: '@types/luxon@3.3.8': resolution: {integrity: sha512-jYvz8UMLDgy3a5SkGJne8H7VA7zPV2Lwohjx0V8V31+SqAjNmurWMkk9cQhfvlcnXWudBpK9xPM1n4rljOcHYQ==} + '@types/luxon@3.4.2': + resolution: {integrity: sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==} + '@types/mime@1.3.5': resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} @@ -810,6 +997,9 @@ packages: resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + abbrev@1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -829,6 +1019,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + adm-zip@0.5.16: + resolution: {integrity: sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==} + engines: {node: '>=12.0'} + agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} @@ -837,6 +1031,14 @@ packages: resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} engines: {node: '>= 14'} + agentkeepalive@4.6.0: + resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} + engines: {node: '>= 8.0.0'} + + aggregate-error@3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -895,10 +1097,18 @@ packages: resolution: {integrity: sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==} engines: {node: '>= 6.0.0'} + aproba@2.0.0: + resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} + archive-type@4.0.0: resolution: {integrity: sha512-zV4Ky0v1F8dBrdYElwTvQhweQ0P7Kwc1aluqJsYtOBP01jXcWCyW2IEfI1YiqsG+Iy7ZR+o5LF1N+PGECBxHWA==} engines: {node: '>=4'} + are-we-there-yet@3.0.1: + resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -962,9 +1172,6 @@ packages: axios@1.7.9: resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==} - b4a@1.6.7: - resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==} - babel-jest@29.7.0: resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -993,21 +1200,6 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - bare-events@2.5.4: - resolution: {integrity: sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==} - - bare-fs@2.3.5: - resolution: {integrity: sha512-SlE9eTxifPDJrT6YgemQ1WGFleevzwY+XAP1Xqgl56HtcrisC2CHCZ2tq6dBpcH2TnNxwUEUGhweo+lrQtYuiw==} - - bare-os@2.4.4: - resolution: {integrity: sha512-z3UiI2yi1mK0sXeRdc4O1Kk8aOa/e+FNWZcTiPB/dfTWyLypuE99LibgRaQki914Jq//yAWylcAt+mknKdixRQ==} - - bare-path@2.1.3: - resolution: {integrity: sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==} - - bare-stream@2.6.1: - resolution: {integrity: sha512-eVZbtKM+4uehzrsj49KtCy3Pbg7kO1pJ3SKZ1SFrIH/0pnj9scuGGgUlNDf/7qS8WKtGdiJY5Kyhs/ivYPTB/g==} - base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -1019,6 +1211,9 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + bl@1.2.3: resolution: {integrity: sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==} @@ -1083,10 +1278,17 @@ packages: buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + bullmq@5.34.10: + resolution: {integrity: sha512-ia6EzpQm1ZPq6GUBSLyfvzJrhdBTd1f3Gn2g9pFtLX4hBOob6QHmcmBzGgPlSCyr/i2Qfe4OdjS21bRd02srbw==} + bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} + cacache@15.3.0: + resolution: {integrity: sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==} + engines: {node: '>= 10'} + cache-content-type@1.0.1: resolution: {integrity: sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==} engines: {node: '>= 6.0.0'} @@ -1167,6 +1369,10 @@ packages: chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + chownr@3.0.0: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} @@ -1187,6 +1393,10 @@ packages: class-validator@0.14.1: resolution: {integrity: sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==} + clean-stack@2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + cli-boxes@2.2.1: resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==} engines: {node: '>=6'} @@ -1225,6 +1435,10 @@ packages: clone-response@1.0.3: resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} + cluster-key-slot@1.1.2: + resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} + engines: {node: '>=0.10.0'} + co-body@6.2.0: resolution: {integrity: sha512-Kbpv2Yd1NdL1V/V4cwLVxraHDV6K8ayohr2rmH0J87Er8+zJjcTa6dAn9QMPC9CRgU8+aNajKbSf1TzDB1yKPA==} engines: {node: '>=8.0.0'} @@ -1252,6 +1466,10 @@ packages: color-string@1.9.1: resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + color@4.2.3: resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} engines: {node: '>=12.5.0'} @@ -1295,6 +1513,9 @@ packages: resolution: {integrity: sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==} engines: {node: '>=8'} + console-control-strings@1.1.0: + resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} @@ -1324,9 +1545,16 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true + cron-parser@4.9.0: + resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==} + engines: {node: '>=12.0.0'} + cron@2.4.4: resolution: {integrity: sha512-MHlPImXJj3K7x7lyUHjtKEOl69CSlTOWxS89jiFgNkzXfvhVjhMz/nc7/EIfN9vgooZp8XTtXJ1FREdmbyXOiQ==} + cron@3.5.0: + resolution: {integrity: sha512-0eYZqCnapmxYcV06uktql93wNWdlTmmBFP2iYz+JPVcQqlyFYcn1lFuIk4R54pkOmE7mcldTAPZv6X5XA4Q46A==} + croner@4.1.97: resolution: {integrity: sha512-/f6gpQuxDaqXu+1kwQYSckUglPaOrHdbIlBAu0YuW8/Cdb45XwXYNUBXg3r/9Mo6n540Kn/smKcZWko5x99KrQ==} @@ -1558,6 +1786,9 @@ packages: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} + encoding@0.1.13: + resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} + end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} @@ -1569,6 +1800,13 @@ packages: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + err-code@2.0.3: + resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} + error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} @@ -1753,9 +1991,6 @@ packages: fast-diff@1.3.0: resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} - fast-fifo@1.3.2: - resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} - fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} @@ -1819,6 +2054,9 @@ packages: resolution: {integrity: sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==} engines: {node: '>=4'} + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + filelist@1.0.4: resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} @@ -1882,6 +2120,10 @@ packages: resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} engines: {node: '>=10'} + fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + fs-readdir-recursive@1.1.0: resolution: {integrity: sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==} @@ -1899,6 +2141,11 @@ packages: functional-red-black-tree@1.0.1: resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==} + gauge@4.0.4: + resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + generate-function@2.3.1: resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==} @@ -2040,6 +2287,9 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} + has-unicode@2.0.1: + resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + has-yarn@2.1.0: resolution: {integrity: sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==} engines: {node: '>=8'} @@ -2087,6 +2337,10 @@ packages: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} + http-proxy-agent@4.0.1: + resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==} + engines: {node: '>= 6'} + http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} @@ -2103,6 +2357,9 @@ packages: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} + humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -2143,6 +2400,9 @@ packages: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} + infer-owner@1.0.4: + resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==} + inflation@2.1.0: resolution: {integrity: sha512-t54PPJHG1Pp7VQvxyVCJ9mBbjG3Hqryges9bXoOO6GExCPa+//i/d5GSuFtpx3ALLd7lgIAur6zrIlBQyJuMlQ==} engines: {node: '>= 0.8.0'} @@ -2176,6 +2436,10 @@ packages: inversify@6.0.1: resolution: {integrity: sha512-B3ex30927698TJENHR++8FfEaJGqoWOgI6ZY5Ht/nLUsFCwHn6akbwtnUAPCgUepAnTpe2qHxhDNjoKLyz6rgQ==} + ioredis@5.4.2: + resolution: {integrity: sha512-0SZXGNGZ+WzISQ67QDyZ2x0+wVxjjUndtD8oSeik/4ajifeiRufed8fCb8QW8VMyi4MXcS+UO1k/0NGhvq1PAg==} + engines: {node: '>=12.22.0'} + ip-address@9.0.5: resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} engines: {node: '>= 12'} @@ -2236,6 +2500,9 @@ packages: resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==} engines: {node: '>=10'} + is-lambda@1.0.1: + resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} + is-nan@1.3.2: resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==} engines: {node: '>= 0.4'} @@ -2628,9 +2895,15 @@ packages: lodash.clonedeep@4.5.0: resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} + lodash.defaults@4.2.0: + resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + lodash.includes@4.3.0: resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + lodash.isarguments@3.1.0: + resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} + lodash.isboolean@3.0.3: resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} @@ -2702,6 +2975,10 @@ packages: resolution: {integrity: sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==} engines: {node: '>=12'} + luxon@3.5.0: + resolution: {integrity: sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==} + engines: {node: '>=12'} + make-dir@1.3.0: resolution: {integrity: sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==} engines: {node: '>=4'} @@ -2721,6 +2998,10 @@ packages: make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + make-fetch-happen@9.1.0: + resolution: {integrity: sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==} + engines: {node: '>= 10'} + makeerror@1.0.12: resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} @@ -2813,10 +3094,42 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass-collect@1.0.2: + resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} + engines: {node: '>= 8'} + + minipass-fetch@1.4.1: + resolution: {integrity: sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==} + engines: {node: '>=8'} + + minipass-flush@1.0.5: + resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} + engines: {node: '>= 8'} + + minipass-pipeline@1.2.4: + resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} + engines: {node: '>=8'} + + minipass-sized@1.0.3: + resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} + engines: {node: '>=8'} + + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + minizlib@3.0.1: resolution: {integrity: sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==} engines: {node: '>= 18'} @@ -2853,6 +3166,13 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + msgpackr-extract@3.0.3: + resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==} + hasBin: true + + msgpackr@1.11.2: + resolution: {integrity: sha512-F9UngXRlPyWCDEASDpTf6c9uNhGPTqnTeLVt7bN+bU1eajoR/8V9ys2BRaV5C/e5ihE6sJ9uPIKaYt6bFuO32g==} + multimatch@5.0.0: resolution: {integrity: sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==} engines: {node: '>=10'} @@ -2920,8 +3240,11 @@ packages: resolution: {integrity: sha512-SZ40vRiy/+wRTf21hxkkEjPJZpARzUMVcJoQse2EF8qkUWbbO2z7vd5oA/H6bVH6SZQ5STGcu0KRDS7biNRfxw==} engines: {node: '>=10'} - node-addon-api@6.1.0: - resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==} + node-abort-controller@3.1.1: + resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} + + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} @@ -2932,12 +3255,26 @@ packages: encoding: optional: true + node-gyp-build-optional-packages@5.2.2: + resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==} + hasBin: true + + node-gyp@8.4.1: + resolution: {integrity: sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==} + engines: {node: '>= 10.12.0'} + hasBin: true + node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} node-releases@2.0.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + nopt@5.0.0: + resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} + engines: {node: '>=6'} + hasBin: true + normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} @@ -2961,6 +3298,11 @@ packages: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} + npmlog@6.0.2: + resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + nssocket@0.6.0: resolution: {integrity: sha512-a9GSOIql5IqgWJR3F/JXG4KpJTA3Z53Cj0MeMvGpglytB1nxE4PdFNC0jINe27CS7cGivoynwc054EzCcT3M3w==} engines: {node: '>= 0.10.x'} @@ -3051,6 +3393,10 @@ packages: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} + p-map@4.0.0: + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + engines: {node: '>=10'} + p-timeout@2.0.1: resolution: {integrity: sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==} engines: {node: '>=4'} @@ -3263,6 +3609,18 @@ packages: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} engines: {node: '>=0.4.0'} + promise-inflight@1.0.1: + resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} + peerDependencies: + bluebird: '*' + peerDependenciesMeta: + bluebird: + optional: true + + promise-retry@2.0.1: + resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} + engines: {node: '>=10'} + promptly@2.2.0: resolution: {integrity: sha512-aC9j+BZsRSSzEsXBNBwDnAxujdx19HycZoKgRgzWnS8eOHg1asuf9heuLprfbe739zY3IdUQx+Egv6Jn135WHA==} @@ -3306,9 +3664,6 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - queue-tick@1.0.1: - resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==} - quick-lru@4.0.1: resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} engines: {node: '>=8'} @@ -3355,6 +3710,14 @@ packages: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} + redis-errors@1.2.0: + resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} + engines: {node: '>=4'} + + redis-parser@3.0.0: + resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} + engines: {node: '>=4'} + reflect-metadata@0.1.13: resolution: {integrity: sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==} @@ -3413,6 +3776,10 @@ packages: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -3426,6 +3793,11 @@ packages: resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} hasBin: true + rimraf@6.0.1: + resolution: {integrity: sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==} + engines: {node: 20 || >=22} + hasBin: true + run-async@2.4.1: resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} engines: {node: '>=0.12.0'} @@ -3492,6 +3864,9 @@ packages: seq-queue@0.0.5: resolution: {integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==} + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -3503,9 +3878,9 @@ packages: resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} hasBin: true - sharp@0.32.6: - resolution: {integrity: sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==} - engines: {node: '>=14.15.0'} + sharp@0.33.5: + resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} @@ -3568,6 +3943,10 @@ packages: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + socks-proxy-agent@6.2.1: + resolution: {integrity: sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==} + engines: {node: '>= 10'} + socks-proxy-agent@8.0.5: resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} engines: {node: '>= 14'} @@ -3619,14 +3998,24 @@ packages: sprintf-js@1.1.3: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + sqlite3@5.1.7: + resolution: {integrity: sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==} + sqlstring@2.3.3: resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==} engines: {node: '>= 0.6'} + ssri@8.0.1: + resolution: {integrity: sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==} + engines: {node: '>= 8'} + stack-utils@2.0.6: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} + standard-as-callback@2.1.0: + resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} + statuses@1.5.0: resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} engines: {node: '>= 0.6'} @@ -3641,9 +4030,6 @@ packages: stream-slice@0.1.2: resolution: {integrity: sha512-QzQxpoacatkreL6jsxnVb7X5R/pGw9OUv2qWTYWnmLpg4NdN31snPy/f3TdQE1ZUXaThRvj1Zw4/OGg0ZkaLMA==} - streamx@2.21.1: - resolution: {integrity: sha512-PhP9wUnFLa+91CPy3N6tiQsK+gnYyUNuk15S3YG/zjYE7RuPeCjJngqnzpC31ow0lzBHQ+QGO4cNJnd0djYUsw==} - strict-uri-encode@1.1.0: resolution: {integrity: sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==} engines: {node: '>=0.10.0'} @@ -3753,9 +4139,6 @@ packages: tar-fs@2.1.1: resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} - tar-fs@3.0.6: - resolution: {integrity: sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==} - tar-stream@1.6.2: resolution: {integrity: sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==} engines: {node: '>= 0.8.0'} @@ -3764,8 +4147,9 @@ packages: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} - tar-stream@3.1.7: - resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} tar@7.4.3: resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} @@ -3775,9 +4159,6 @@ packages: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} - text-decoder@1.2.3: - resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} - text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -4007,6 +4388,12 @@ packages: undici-types@6.20.0: resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + unique-filename@1.1.1: + resolution: {integrity: sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==} + + unique-slug@2.0.2: + resolution: {integrity: sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==} + unique-string@2.0.0: resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} engines: {node: '>=8'} @@ -4094,6 +4481,9 @@ packages: engines: {node: '>= 8'} hasBin: true + wide-align@1.1.5: + resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + widest-line@3.1.0: resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} engines: {node: '>=8'} @@ -4425,10 +4815,9 @@ snapshots: dependencies: '@cool-midway/cache-manager-fs-hash': 7.0.0 '@midwayjs/cache': 3.14.0 - '@midwayjs/cache-manager': 3.19.3 + '@midwayjs/cache-manager': 3.20.0 axios: 1.7.9 commander: 13.0.0 - decompress: 4.2.1 download: 8.0.0 glob: 11.0.1 javascript-obfuscator: 4.1.1 @@ -4446,6 +4835,18 @@ snapshots: - supports-color - utf-8-validate + '@cool-midway/task@file:../midway-packages/task': + dependencies: + bullmq: 5.34.10 + ioredis: 5.4.2 + transitivePeerDependencies: + - supports-color + + '@emnapi/runtime@1.3.1': + dependencies: + tslib: 2.8.1 + optional: true + '@eslint-community/eslint-utils@4.4.1(eslint@7.32.0)': dependencies: eslint: 7.32.0 @@ -4467,6 +4868,9 @@ snapshots: transitivePeerDependencies: - supports-color + '@gar/promisify@1.1.3': + optional: true + '@hapi/bourne@3.0.0': {} '@hapi/hoek@9.3.0': {} @@ -4485,6 +4889,83 @@ snapshots: '@humanwhocodes/object-schema@1.2.1': {} + '@img/sharp-darwin-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.0.4 + optional: true + + '@img/sharp-darwin-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.0.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.0.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.0.5': + optional: true + + '@img/sharp-libvips-linux-s390x@1.0.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.0.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + optional: true + + '@img/sharp-linux-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.0.4 + optional: true + + '@img/sharp-linux-arm@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.0.5 + optional: true + + '@img/sharp-linux-s390x@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.0.4 + optional: true + + '@img/sharp-linux-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.0.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + optional: true + + '@img/sharp-wasm32@0.33.5': + dependencies: + '@emnapi/runtime': 1.3.1 + optional: true + + '@img/sharp-win32-ia32@0.33.5': + optional: true + + '@img/sharp-win32-x64@0.33.5': + optional: true + + '@ioredis/commands@1.2.0': {} + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -4708,16 +5189,16 @@ snapshots: transitivePeerDependencies: - supports-color - '@midwayjs/async-hooks-context-manager@3.19.0': {} + '@midwayjs/async-hooks-context-manager@3.20.0': {} - '@midwayjs/bootstrap@3.19.3': + '@midwayjs/bootstrap@3.20.0': dependencies: - '@midwayjs/async-hooks-context-manager': 3.19.0 + '@midwayjs/async-hooks-context-manager': 3.20.0 '@midwayjs/event-bus': 1.11.1 '@midwayjs/bundle-helper@1.3.0': {} - '@midwayjs/cache-manager@3.19.3': + '@midwayjs/cache-manager@3.20.0': dependencies: lodash.clonedeep: 4.5.0 lru-cache: 7.18.3 @@ -4731,18 +5212,18 @@ snapshots: scmp: 2.1.0 should-send-same-site-none: 2.0.5 - '@midwayjs/core@3.19.0': + '@midwayjs/core@3.20.0': dependencies: '@midwayjs/glob': 1.1.1 class-transformer: 0.5.1 picomatch: 2.3.1 reflect-metadata: 0.2.2 - '@midwayjs/cron@3.19.2': + '@midwayjs/cron@3.20.0': dependencies: cron: 2.4.4 - '@midwayjs/cross-domain@3.19.3': + '@midwayjs/cross-domain@3.20.0': dependencies: vary: 1.1.2 @@ -4752,20 +5233,20 @@ snapshots: dependencies: picomatch: 2.3.1 - '@midwayjs/i18n@3.19.2': + '@midwayjs/i18n@3.20.0': dependencies: picomatch: 2.3.1 - '@midwayjs/info@3.19.2': + '@midwayjs/info@3.20.0': dependencies: picomatch: 2.3.1 - '@midwayjs/koa@3.19.2': + '@midwayjs/koa@3.20.0': dependencies: '@koa/router': 12.0.2 '@midwayjs/cookies': 1.2.0 - '@midwayjs/core': 3.19.0 - '@midwayjs/session': 3.19.2 + '@midwayjs/core': 3.20.0 + '@midwayjs/session': 3.20.0 '@types/koa': 2.15.0 '@types/qs': 6.9.17 koa: 2.15.3 @@ -4779,9 +5260,9 @@ snapshots: dayjs: 1.11.13 safe-stable-stringify: 2.5.0 - '@midwayjs/mock@3.19.2': + '@midwayjs/mock@3.20.0': dependencies: - '@midwayjs/async-hooks-context-manager': 3.19.0 + '@midwayjs/async-hooks-context-manager': 3.20.0 '@types/superagent': 4.1.14 '@types/supertest': 2.0.16 js-yaml: 4.1.0 @@ -4790,11 +5271,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@midwayjs/session@3.19.2': + '@midwayjs/session@3.20.0': dependencies: '@midwayjs/cookies': 1.2.0 - '@midwayjs/static-file@3.19.3': + '@midwayjs/static-file@3.20.0': dependencies: koa-range: 0.3.0 koa-static-cache: 5.1.4 @@ -4802,24 +5283,42 @@ snapshots: transitivePeerDependencies: - supports-color - '@midwayjs/typeorm@3.19.2': {} + '@midwayjs/typeorm@3.20.0': {} - '@midwayjs/upload@3.19.3': + '@midwayjs/upload@3.20.0': dependencies: file-type: 16.5.4 raw-body: 2.5.2 - '@midwayjs/validate@3.19.2': + '@midwayjs/validate@3.20.0': dependencies: - '@midwayjs/i18n': 3.19.2 + '@midwayjs/i18n': 3.20.0 joi: 17.13.3 - '@midwayjs/view-ejs@3.19.2': + '@midwayjs/view-ejs@3.20.0': dependencies: - '@midwayjs/view': 3.19.2 + '@midwayjs/view': 3.20.0 ejs: 3.1.10 - '@midwayjs/view@3.19.2': {} + '@midwayjs/view@3.20.0': {} + + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': + optional: true '@nodelib/fs.scandir@2.1.5': dependencies: @@ -4833,6 +5332,18 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.18.0 + '@npmcli/fs@1.1.1': + dependencies: + '@gar/promisify': 1.1.3 + semver: 7.6.3 + optional: true + + '@npmcli/move-file@1.1.2': + dependencies: + mkdirp: 1.0.4 + rimraf: 3.0.2 + optional: true + '@pkgjs/parseargs@0.11.0': optional: true @@ -4917,6 +5428,9 @@ snapshots: '@tokenizer/token@0.3.0': {} + '@tootallnate/once@1.1.2': + optional: true + '@tootallnate/quickjs-emscripten@0.23.0': {} '@types/accepts@1.3.7': @@ -5026,6 +5540,8 @@ snapshots: '@types/luxon@3.3.8': {} + '@types/luxon@3.4.2': {} + '@types/mime@1.3.5': {} '@types/minimatch@3.0.5': {} @@ -5162,6 +5678,9 @@ snapshots: '@typescript-eslint/types': 5.62.0 eslint-visitor-keys: 3.4.3 + abbrev@1.1.1: + optional: true + accepts@1.3.8: dependencies: mime-types: 2.1.35 @@ -5175,6 +5694,8 @@ snapshots: acorn@8.8.2: {} + adm-zip@0.5.16: {} + agent-base@6.0.2: dependencies: debug: 4.4.0 @@ -5183,6 +5704,17 @@ snapshots: agent-base@7.1.3: {} + agentkeepalive@4.6.0: + dependencies: + humanize-ms: 1.2.1 + optional: true + + aggregate-error@3.1.0: + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + optional: true + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -5238,10 +5770,19 @@ snapshots: app-root-path@3.1.0: {} + aproba@2.0.0: + optional: true + archive-type@4.0.0: dependencies: file-type: 4.4.0 + are-we-there-yet@3.0.1: + dependencies: + delegates: 1.0.0 + readable-stream: 3.6.2 + optional: true + argparse@1.0.10: dependencies: sprintf-js: 1.0.3 @@ -5297,8 +5838,6 @@ snapshots: transitivePeerDependencies: - debug - b4a@1.6.7: {} - babel-jest@29.7.0(@babel/core@7.26.0): dependencies: '@babel/core': 7.26.0 @@ -5356,35 +5895,16 @@ snapshots: balanced-match@1.0.2: {} - bare-events@2.5.4: - optional: true - - bare-fs@2.3.5: - dependencies: - bare-events: 2.5.4 - bare-path: 2.1.3 - bare-stream: 2.6.1 - optional: true - - bare-os@2.4.4: - optional: true - - bare-path@2.1.3: - dependencies: - bare-os: 2.4.4 - optional: true - - bare-stream@2.6.1: - dependencies: - streamx: 2.21.1 - optional: true - base64-js@1.5.1: {} basic-ftp@5.0.5: {} binary-extensions@2.3.0: {} + bindings@1.5.0: + dependencies: + file-uri-to-path: 1.0.0 + bl@1.2.3: dependencies: readable-stream: 2.3.8 @@ -5464,8 +5984,44 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + bullmq@5.34.10: + dependencies: + cron-parser: 4.9.0 + ioredis: 5.4.2 + msgpackr: 1.11.2 + node-abort-controller: 3.1.1 + semver: 7.6.3 + tslib: 2.8.1 + uuid: 9.0.1 + transitivePeerDependencies: + - supports-color + bytes@3.1.2: {} + cacache@15.3.0: + dependencies: + '@npmcli/fs': 1.1.1 + '@npmcli/move-file': 1.1.2 + chownr: 2.0.0 + fs-minipass: 2.1.0 + glob: 7.2.3 + infer-owner: 1.0.4 + lru-cache: 6.0.0 + minipass: 3.3.6 + minipass-collect: 1.0.2 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + mkdirp: 1.0.4 + p-map: 4.0.0 + promise-inflight: 1.0.1 + rimraf: 3.0.2 + ssri: 8.0.1 + tar: 6.2.1 + unique-filename: 1.1.1 + transitivePeerDependencies: + - bluebird + optional: true + cache-content-type@1.0.1: dependencies: mime-types: 2.1.35 @@ -5568,6 +6124,8 @@ snapshots: chownr@1.1.4: {} + chownr@2.0.0: {} + chownr@3.0.0: {} ci-info@2.0.0: {} @@ -5584,6 +6142,9 @@ snapshots: libphonenumber-js: 1.11.17 validator: 13.12.0 + clean-stack@2.2.0: + optional: true + cli-boxes@2.2.1: {} cli-cursor@3.1.0: @@ -5631,6 +6192,8 @@ snapshots: dependencies: mimic-response: 1.0.1 + cluster-key-slot@1.1.2: {} + co-body@6.2.0: dependencies: '@hapi/bourne': 3.0.0 @@ -5660,6 +6223,9 @@ snapshots: color-name: 1.1.4 simple-swizzle: 0.2.2 + color-support@1.1.3: + optional: true + color@4.2.3: dependencies: color-convert: 2.0.1 @@ -5698,6 +6264,9 @@ snapshots: write-file-atomic: 3.0.3 xdg-basedir: 4.0.0 + console-control-strings@1.1.0: + optional: true + content-disposition@0.5.4: dependencies: safe-buffer: 5.2.1 @@ -5732,11 +6301,20 @@ snapshots: - supports-color - ts-node + cron-parser@4.9.0: + dependencies: + luxon: 3.3.0 + cron@2.4.4: dependencies: '@types/luxon': 3.3.8 luxon: 3.3.0 + cron@3.5.0: + dependencies: + '@types/luxon': 3.4.2 + luxon: 3.5.0 + croner@4.1.97: {} cross-env@7.0.3: @@ -5939,6 +6517,11 @@ snapshots: encodeurl@1.0.2: {} + encoding@0.1.13: + dependencies: + iconv-lite: 0.6.3 + optional: true + end-of-stream@1.4.4: dependencies: once: 1.4.0 @@ -5952,6 +6535,12 @@ snapshots: ansi-colors: 4.1.3 strip-ansi: 6.0.1 + env-paths@2.2.1: + optional: true + + err-code@2.0.3: + optional: true + error-ex@1.3.2: dependencies: is-arrayish: 0.2.1 @@ -6158,8 +6747,6 @@ snapshots: fast-diff@1.3.0: {} - fast-fifo@1.3.2: {} - fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -6216,6 +6803,8 @@ snapshots: file-type@6.2.0: {} + file-uri-to-path@1.0.0: {} + filelist@1.0.4: dependencies: minimatch: 5.1.6 @@ -6287,6 +6876,10 @@ snapshots: jsonfile: 6.1.0 universalify: 2.0.1 + fs-minipass@2.1.0: + dependencies: + minipass: 3.3.6 + fs-readdir-recursive@1.1.0: {} fs.realpath@1.0.0: {} @@ -6298,6 +6891,18 @@ snapshots: functional-red-black-tree@1.0.1: {} + gauge@4.0.4: + dependencies: + aproba: 2.0.0 + color-support: 1.1.3 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + signal-exit: 3.0.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wide-align: 1.1.5 + optional: true + generate-function@2.3.1: dependencies: is-property: 1.0.2 @@ -6475,6 +7080,9 @@ snapshots: dependencies: has-symbols: 1.1.0 + has-unicode@2.0.1: + optional: true + has-yarn@2.1.0: {} has@1.0.4: {} @@ -6520,6 +7128,15 @@ snapshots: statuses: 2.0.1 toidentifier: 1.0.1 + http-proxy-agent@4.0.1: + dependencies: + '@tootallnate/once': 1.1.2 + agent-base: 6.0.2 + debug: 4.4.0 + transitivePeerDependencies: + - supports-color + optional: true + http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.3 @@ -6543,6 +7160,11 @@ snapshots: human-signals@2.1.0: {} + humanize-ms@1.2.1: + dependencies: + ms: 2.1.3 + optional: true + iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 @@ -6573,6 +7195,9 @@ snapshots: indent-string@4.0.0: {} + infer-owner@1.0.4: + optional: true + inflation@2.1.0: {} inflight@1.0.6: @@ -6614,6 +7239,20 @@ snapshots: inversify@6.0.1: {} + ioredis@5.4.2: + dependencies: + '@ioredis/commands': 1.2.0 + cluster-key-slot: 1.1.2 + debug: 4.4.0 + denque: 2.1.0 + lodash.defaults: 4.2.0 + lodash.isarguments: 3.1.0 + redis-errors: 1.2.0 + redis-parser: 3.0.0 + standard-as-callback: 2.1.0 + transitivePeerDependencies: + - supports-color + ip-address@9.0.5: dependencies: jsbn: 1.1.0 @@ -6670,6 +7309,9 @@ snapshots: global-dirs: 3.0.1 is-path-inside: 3.0.3 + is-lambda@1.0.1: + optional: true + is-nan@1.3.2: dependencies: call-bind: 1.0.8 @@ -7301,8 +7943,12 @@ snapshots: lodash.clonedeep@4.5.0: {} + lodash.defaults@4.2.0: {} + lodash.includes@4.3.0: {} + lodash.isarguments@3.1.0: {} + lodash.isboolean@3.0.3: {} lodash.isinteger@4.0.4: {} @@ -7349,6 +7995,8 @@ snapshots: luxon@3.3.0: {} + luxon@3.5.0: {} + make-dir@1.3.0: dependencies: pify: 3.0.0 @@ -7368,6 +8016,29 @@ snapshots: make-error@1.3.6: {} + make-fetch-happen@9.1.0: + dependencies: + agentkeepalive: 4.6.0 + cacache: 15.3.0 + http-cache-semantics: 4.1.1 + http-proxy-agent: 4.0.1 + https-proxy-agent: 5.0.1 + is-lambda: 1.0.1 + lru-cache: 6.0.0 + minipass: 3.3.6 + minipass-collect: 1.0.2 + minipass-fetch: 1.4.1 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + negotiator: 0.6.3 + promise-retry: 2.0.1 + socks-proxy-agent: 6.2.1 + ssri: 8.0.1 + transitivePeerDependencies: + - bluebird + - supports-color + optional: true + makeerror@1.0.12: dependencies: tmpl: 1.0.5 @@ -7452,8 +8123,48 @@ snapshots: minimist@1.2.8: {} + minipass-collect@1.0.2: + dependencies: + minipass: 3.3.6 + optional: true + + minipass-fetch@1.4.1: + dependencies: + minipass: 3.3.6 + minipass-sized: 1.0.3 + minizlib: 2.1.2 + optionalDependencies: + encoding: 0.1.13 + optional: true + + minipass-flush@1.0.5: + dependencies: + minipass: 3.3.6 + optional: true + + minipass-pipeline@1.2.4: + dependencies: + minipass: 3.3.6 + optional: true + + minipass-sized@1.0.3: + dependencies: + minipass: 3.3.6 + optional: true + + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + + minipass@5.0.0: {} + minipass@7.1.2: {} + minizlib@2.1.2: + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + minizlib@3.0.1: dependencies: minipass: 7.1.2 @@ -7475,6 +8186,22 @@ snapshots: ms@2.1.3: {} + msgpackr-extract@3.0.3: + dependencies: + node-gyp-build-optional-packages: 5.2.2 + optionalDependencies: + '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.3 + optional: true + + msgpackr@1.11.2: + optionalDependencies: + msgpackr-extract: 3.0.3 + multimatch@5.0.0: dependencies: '@types/minimatch': 3.0.5 @@ -7570,16 +8297,47 @@ snapshots: dependencies: semver: 7.6.3 - node-addon-api@6.1.0: {} + node-abort-controller@3.1.1: {} - node-fetch@2.7.0: + node-addon-api@7.1.1: {} + + node-fetch@2.7.0(encoding@0.1.13): dependencies: whatwg-url: 5.0.0 + optionalDependencies: + encoding: 0.1.13 + + node-gyp-build-optional-packages@5.2.2: + dependencies: + detect-libc: 2.0.3 + optional: true + + node-gyp@8.4.1: + dependencies: + env-paths: 2.2.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + make-fetch-happen: 9.1.0 + nopt: 5.0.0 + npmlog: 6.0.2 + rimraf: 3.0.2 + semver: 7.6.3 + tar: 6.2.1 + which: 2.0.2 + transitivePeerDependencies: + - bluebird + - supports-color + optional: true node-int64@0.4.0: {} node-releases@2.0.19: {} + nopt@5.0.0: + dependencies: + abbrev: 1.1.1 + optional: true + normalize-package-data@2.5.0: dependencies: hosted-git-info: 2.8.9 @@ -7608,6 +8366,14 @@ snapshots: dependencies: path-key: 3.1.1 + npmlog@6.0.2: + dependencies: + are-we-there-yet: 3.0.1 + console-control-strings: 1.1.0 + gauge: 4.0.4 + set-blocking: 2.0.0 + optional: true + nssocket@0.6.0: dependencies: eventemitter2: 0.4.14 @@ -7690,6 +8456,11 @@ snapshots: dependencies: p-limit: 2.3.0 + p-map@4.0.0: + dependencies: + aggregate-error: 3.1.0 + optional: true + p-timeout@2.0.1: dependencies: p-finally: 1.0.0 @@ -7803,12 +8574,12 @@ snapshots: dependencies: find-up: 4.1.0 - pkg-fetch@3.4.2: + pkg-fetch@3.4.2(encoding@0.1.13): dependencies: chalk: 4.1.2 fs-extra: 9.1.0 https-proxy-agent: 5.0.1 - node-fetch: 2.7.0 + node-fetch: 2.7.0(encoding@0.1.13) progress: 2.0.3 semver: 7.6.3 tar-fs: 2.1.1 @@ -7817,7 +8588,7 @@ snapshots: - encoding - supports-color - pkg@5.8.1: + pkg@5.8.1(encoding@0.1.13): dependencies: '@babel/generator': 7.18.2 '@babel/parser': 7.18.4 @@ -7829,7 +8600,7 @@ snapshots: is-core-module: 2.9.0 minimist: 1.2.8 multistream: 4.1.0 - pkg-fetch: 3.4.2 + pkg-fetch: 3.4.2(encoding@0.1.13) prebuild-install: 7.1.1 resolve: 1.22.10 stream-meter: 1.0.4 @@ -7955,6 +8726,15 @@ snapshots: progress@2.0.3: {} + promise-inflight@1.0.1: + optional: true + + promise-retry@2.0.1: + dependencies: + err-code: 2.0.3 + retry: 0.12.0 + optional: true + promptly@2.2.0: dependencies: read: 1.0.7 @@ -8006,8 +8786,6 @@ snapshots: queue-microtask@1.2.3: {} - queue-tick@1.0.1: {} - quick-lru@4.0.1: {} raw-body@2.5.2: @@ -8072,6 +8850,12 @@ snapshots: indent-string: 4.0.0 strip-indent: 3.0.0 + redis-errors@1.2.0: {} + + redis-parser@3.0.0: + dependencies: + redis-errors: 1.2.0 + reflect-metadata@0.1.13: {} reflect-metadata@0.2.2: {} @@ -8123,6 +8907,9 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 + retry@0.12.0: + optional: true + reusify@1.0.4: {} rimraf@3.0.2: @@ -8133,6 +8920,11 @@ snapshots: dependencies: glob: 10.4.5 + rimraf@6.0.1: + dependencies: + glob: 11.0.1 + package-json-from-dist: 1.0.1 + run-async@2.4.1: {} run-parallel@1.2.0: @@ -8183,6 +8975,9 @@ snapshots: seq-queue@0.0.5: {} + set-blocking@2.0.0: + optional: true + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -8199,16 +8994,31 @@ snapshots: inherits: 2.0.4 safe-buffer: 5.2.1 - sharp@0.32.6: + sharp@0.33.5: dependencies: color: 4.2.3 detect-libc: 2.0.3 - node-addon-api: 6.1.0 - prebuild-install: 7.1.1 semver: 7.6.3 - simple-get: 4.0.1 - tar-fs: 3.0.6 - tunnel-agent: 0.6.0 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.33.5 + '@img/sharp-darwin-x64': 0.33.5 + '@img/sharp-libvips-darwin-arm64': 1.0.4 + '@img/sharp-libvips-darwin-x64': 1.0.4 + '@img/sharp-libvips-linux-arm': 1.0.5 + '@img/sharp-libvips-linux-arm64': 1.0.4 + '@img/sharp-libvips-linux-s390x': 1.0.4 + '@img/sharp-libvips-linux-x64': 1.0.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + '@img/sharp-linux-arm': 0.33.5 + '@img/sharp-linux-arm64': 0.33.5 + '@img/sharp-linux-s390x': 0.33.5 + '@img/sharp-linux-x64': 0.33.5 + '@img/sharp-linuxmusl-arm64': 0.33.5 + '@img/sharp-linuxmusl-x64': 0.33.5 + '@img/sharp-wasm32': 0.33.5 + '@img/sharp-win32-ia32': 0.33.5 + '@img/sharp-win32-x64': 0.33.5 shebang-command@2.0.0: dependencies: @@ -8276,6 +9086,15 @@ snapshots: smart-buffer@4.2.0: {} + socks-proxy-agent@6.2.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.0 + socks: 2.8.3 + transitivePeerDependencies: + - supports-color + optional: true + socks-proxy-agent@8.0.5: dependencies: agent-base: 7.1.3 @@ -8333,12 +9152,31 @@ snapshots: sprintf-js@1.1.3: {} + sqlite3@5.1.7: + dependencies: + bindings: 1.5.0 + node-addon-api: 7.1.1 + prebuild-install: 7.1.1 + tar: 6.2.1 + optionalDependencies: + node-gyp: 8.4.1 + transitivePeerDependencies: + - bluebird + - supports-color + sqlstring@2.3.3: {} + ssri@8.0.1: + dependencies: + minipass: 3.3.6 + optional: true + stack-utils@2.0.6: dependencies: escape-string-regexp: 2.0.0 + standard-as-callback@2.1.0: {} + statuses@1.5.0: {} statuses@2.0.1: {} @@ -8349,14 +9187,6 @@ snapshots: stream-slice@0.1.2: {} - streamx@2.21.1: - dependencies: - fast-fifo: 1.3.2 - queue-tick: 1.0.1 - text-decoder: 1.2.3 - optionalDependencies: - bare-events: 2.5.4 - strict-uri-encode@1.1.0: {} string-length@4.0.2: @@ -8481,14 +9311,6 @@ snapshots: pump: 3.0.2 tar-stream: 2.2.0 - tar-fs@3.0.6: - dependencies: - pump: 3.0.2 - tar-stream: 3.1.7 - optionalDependencies: - bare-fs: 2.3.5 - bare-path: 2.1.3 - tar-stream@1.6.2: dependencies: bl: 1.2.3 @@ -8507,11 +9329,14 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - tar-stream@3.1.7: + tar@6.2.1: dependencies: - b4a: 1.6.7 - fast-fifo: 1.3.2 - streamx: 2.21.1 + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 tar@7.4.3: dependencies: @@ -8528,10 +9353,6 @@ snapshots: glob: 7.2.3 minimatch: 3.1.2 - text-decoder@1.2.3: - dependencies: - b4a: 1.6.7 - text-table@0.2.0: {} thenify-all@1.6.0: @@ -8662,7 +9483,7 @@ snapshots: dependencies: is-typedarray: 1.0.0 - typeorm@0.3.20(mysql2@3.12.0): + typeorm@0.3.20(ioredis@5.4.2)(mysql2@3.12.0)(sqlite3@5.1.7): dependencies: '@sqltools/formatter': 1.2.5 app-root-path: 3.1.0 @@ -8680,7 +9501,9 @@ snapshots: uuid: 9.0.1 yargs: 17.7.2 optionalDependencies: + ioredis: 5.4.2 mysql2: 3.12.0 + sqlite3: 5.1.7 transitivePeerDependencies: - supports-color @@ -8693,6 +9516,16 @@ snapshots: undici-types@6.20.0: {} + unique-filename@1.1.1: + dependencies: + unique-slug: 2.0.2 + optional: true + + unique-slug@2.0.2: + dependencies: + imurmurhash: 0.1.4 + optional: true + unique-string@2.0.0: dependencies: crypto-random-string: 2.0.0 @@ -8796,6 +9629,11 @@ snapshots: dependencies: isexe: 2.0.0 + wide-align@1.1.5: + dependencies: + string-width: 4.2.3 + optional: true + widest-line@3.1.0: dependencies: string-width: 4.2.3 diff --git a/src/comm/path.ts b/src/comm/path.ts new file mode 100644 index 0000000..4254166 --- /dev/null +++ b/src/comm/path.ts @@ -0,0 +1,58 @@ +import * as path from 'path'; +import * as os from 'os'; +import * as md5 from 'md5'; +import * as fs from 'fs'; + +/** + * 获得配置文件中的 keys + * @returns + */ +const getKeys = () => { + const configFile = path.join(__dirname, '../config/config.default.js'); + const configContent = fs.readFileSync(configFile, 'utf8'); + const keys = configContent.match(/keys: '([^']+)'/)?.[1]; + return keys; +}; + +/** + * 项目数据目录 + * @returns + */ +export const pDataPath = () => { + const dirPath = path.join(os.homedir(), '.cool-admin', md5(getKeys())); + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); + } + return dirPath; +}; + +/** + * 上传目录 + * @returns + */ +export const pUploadPath = () => { + const uploadPath = path.join(pDataPath(), 'upload'); + if (!fs.existsSync(uploadPath)) { + fs.mkdirSync(uploadPath, { recursive: true }); + } + return uploadPath; +}; + +/** + * 插件目录 + * @returns + */ +export const pPluginPath = () => { + const pluginPath = path.join(pDataPath(), 'plugin'); + if (!fs.existsSync(pluginPath)) { + fs.mkdirSync(pluginPath, { recursive: true }); + } + return pluginPath; +}; + +/** + * sqlite 数据库文件 + */ +export const pSqlitePath = () => { + return path.join(pDataPath(), 'cool.sqlite'); +}; diff --git a/src/config/config.default.ts b/src/config/config.default.ts index 338ba23..970c620 100644 --- a/src/config/config.default.ts +++ b/src/config/config.default.ts @@ -2,13 +2,13 @@ import { CoolConfig } from '@cool-midway/core'; import { MidwayConfig } from '@midwayjs/core'; import { CoolCacheStore } from '@cool-midway/core'; import * as path from 'path'; -import { getUploadDir } from '../modules/plugin/hooks/upload'; +import { pUploadPath } from '../comm/path'; // redis缓存 // import { redisStore } from 'cache-manager-ioredis-yet'; export default { - // use for cookie sign key, should change to your own and keep security + // 确保每个项目唯一,项目首次启动会自动生成 keys: '576848ea-bb0c-4c0c-ac95-c8602ef908b5', koa: { port: 8001, @@ -18,12 +18,12 @@ export default { buffer: true, dirs: { default: { - prefix: '/public', + prefix: '/', dir: path.join(__dirname, '..', '..', 'public'), }, static: { prefix: '/upload', - dir: getUploadDir(), + dir: pUploadPath(), }, }, }, @@ -61,6 +61,13 @@ export default { cool: { // 已经插件化,本地文件上传查看 plugin/config.ts,其他云存储查看对应插件的使用 file: {}, + // redis配置 + redis: { + port: 6379, + host: '127.0.0.1', + password: '', + db: 0, + }, // crud配置 crud: { // 插入模式,save不会校验字段(允许传入不存在的字段),insert会校验字段 diff --git a/src/config/config.local.ts b/src/config/config.local.ts index f15c2f6..60cd94d 100644 --- a/src/config/config.local.ts +++ b/src/config/config.local.ts @@ -1,5 +1,7 @@ import { CoolConfig } from '@cool-midway/core'; import { MidwayConfig } from '@midwayjs/core'; +import { pSqlitePath } from '../comm/path'; +import { entities } from '../entities'; /** * 本地开发 npm run dev 读取的配置文件 @@ -8,26 +10,16 @@ export default { typeorm: { dataSource: { default: { - type: 'mysql', - host: '192.168.0.119', - port: 3306, - username: 'root', - password: '123456', - database: 'cool', + type: 'sqlite', + // 数据库文件地址 + database: pSqlitePath(), // 自动建表 注意:线上部署的时候不要使用,有可能导致数据丢失 synchronize: true, // 打印日志 - logging: false, - // 字符集 - charset: 'utf8mb4', - // 是否开启缓存 - cache: true, + logging: true, // 实体路径 - entities: ['**/modules/*/entity'], + entities, // 扩展配置 - extra: { - keepAliveInitialDelay: 10000, - }, }, }, }, diff --git a/src/config/config.prod.ts b/src/config/config.prod.ts index 282c726..796966e 100644 --- a/src/config/config.prod.ts +++ b/src/config/config.prod.ts @@ -1,6 +1,7 @@ import { CoolConfig } from '@cool-midway/core'; import { MidwayConfig } from '@midwayjs/core'; import { entities } from '../entities'; +import { pSqlitePath } from '../comm/path'; /** * 本地开发 npm run prod 读取的配置文件 */ @@ -8,31 +9,26 @@ export default { typeorm: { dataSource: { default: { - type: 'mysql', - host: '192.168.0.119', - port: 3306, - username: 'root', - password: '123456', - database: 'cool', + type: 'sqlite', + // 数据库文件地址 + database: pSqlitePath(), // 自动建表 注意:线上部署的时候不要使用,有可能导致数据丢失 synchronize: false, // 打印日志 logging: false, - // 字符集 - charset: 'utf8mb4', - // 是否开启缓存 - cache: true, // 实体路径 entities, - // 扩展配置 - extra: { - keepAliveInitialDelay: 10000, - }, }, }, }, cool: { - // 是否自动导入数据库,生产环境不建议开,用本地的数据库手动初始化 - initDB: false, + // 实体与路径,跟生成代码、前端请求、swagger文档相关 注意:线上不建议开启,以免暴露敏感信息 + eps: false, + // 是否自动导入模块数据库 + initDB: true, + // 判断是否初始化的方式 + initJudge: 'db', + // 是否自动导入模块菜单 + initMenu: true, } as CoolConfig, } as MidwayConfig; diff --git a/src/configuration.ts b/src/configuration.ts index 52420d4..6acdf9f 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -18,7 +18,7 @@ import * as LocalConfig from './config/config.local'; import * as ProdConfig from './config/config.prod'; import * as cool from '@cool-midway/core'; import * as upload from '@midwayjs/upload'; -import * as os from 'os'; +import * as task from '@cool-midway/task'; @Configuration({ imports: [ @@ -38,6 +38,8 @@ import * as os from 'os'; upload, // cool-admin 官方组件 https://cool-js.com cool, + // 任务与队列 + // task, { component: info, enabledEnvironment: ['local', 'prod'], diff --git a/src/entities.ts b/src/entities.ts index 5b2a4bd..0f64c6b 100644 --- a/src/entities.ts +++ b/src/entities.ts @@ -1,2 +1,45 @@ // 自动生成的文件,请勿手动修改 -export const entities = []; +import * as entity0 from './modules/user/entity/wx'; +import * as entity1 from './modules/user/entity/info'; +import * as entity2 from './modules/user/entity/address'; +import * as entity3 from './modules/task/entity/log'; +import * as entity4 from './modules/task/entity/info'; +import * as entity5 from './modules/space/entity/type'; +import * as entity6 from './modules/space/entity/info'; +import * as entity7 from './modules/recycle/entity/data'; +import * as entity8 from './modules/plugin/entity/info'; +import * as entity9 from './modules/dict/entity/type'; +import * as entity10 from './modules/dict/entity/info'; +import * as entity11 from './modules/base/entity/sys/user_role'; +import * as entity12 from './modules/base/entity/sys/user'; +import * as entity13 from './modules/base/entity/sys/role_menu'; +import * as entity14 from './modules/base/entity/sys/role_department'; +import * as entity15 from './modules/base/entity/sys/role'; +import * as entity16 from './modules/base/entity/sys/param'; +import * as entity17 from './modules/base/entity/sys/menu'; +import * as entity18 from './modules/base/entity/sys/log'; +import * as entity19 from './modules/base/entity/sys/department'; +import * as entity20 from './modules/base/entity/sys/conf'; +export const entities = [ + ...Object.values(entity0), + ...Object.values(entity1), + ...Object.values(entity2), + ...Object.values(entity3), + ...Object.values(entity4), + ...Object.values(entity5), + ...Object.values(entity6), + ...Object.values(entity7), + ...Object.values(entity8), + ...Object.values(entity9), + ...Object.values(entity10), + ...Object.values(entity11), + ...Object.values(entity12), + ...Object.values(entity13), + ...Object.values(entity14), + ...Object.values(entity15), + ...Object.values(entity16), + ...Object.values(entity17), + ...Object.values(entity18), + ...Object.values(entity19), + ...Object.values(entity20), +]; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..ca58f0a --- /dev/null +++ b/src/index.ts @@ -0,0 +1,107 @@ +/** This file generated by @midwayjs/bundle-helper */ + export { MainConfiguration as Configuration } from './configuration'; +export * from './comm/path'; +export * from './comm/utils'; +export * from './config/config.default'; +export * from './modules/user/entity/wx'; +export * from './modules/user/entity/info'; +export * from './modules/user/entity/address'; +export * from './modules/task/entity/log'; +export * from './modules/task/entity/info'; +export * from './modules/space/entity/type'; +export * from './modules/space/entity/info'; +export * from './modules/recycle/entity/data'; +export * from './modules/plugin/entity/info'; +export * from './modules/dict/entity/type'; +export * from './modules/dict/entity/info'; +export * from './modules/base/entity/sys/user_role'; +export * from './modules/base/entity/sys/user'; +export * from './modules/base/entity/sys/role_menu'; +export * from './modules/base/entity/sys/role_department'; +export * from './modules/base/entity/sys/role'; +export * from './modules/base/entity/sys/param'; +export * from './modules/base/entity/sys/menu'; +export * from './modules/base/entity/sys/log'; +export * from './modules/base/entity/sys/department'; +export * from './modules/base/entity/sys/conf'; +export * from './entities'; +export * from './config/config.local'; +export * from './config/config.prod'; +export * from './interface'; +export * from './modules/base/service/sys/conf'; +export * from './modules/base/service/sys/log'; +export * from './modules/base/middleware/log'; +export * from './modules/base/middleware/authority'; +export * from './modules/base/config'; +export * from './modules/plugin/interface'; +export * from './modules/plugin/service/center'; +export * from './modules/plugin/event/init'; +export * from './modules/plugin/service/types'; +export * from './modules/plugin/service/info'; +export * from './modules/base/dto/login'; +export * from './modules/base/service/sys/data'; +export * from './modules/base/service/sys/menu'; +export * from './modules/base/service/sys/department'; +export * from './modules/base/service/sys/perms'; +export * from './modules/base/service/sys/role'; +export * from './modules/base/service/sys/login'; +export * from './modules/base/service/sys/user'; +export * from './modules/base/controller/admin/comm'; +export * from './modules/base/service/sys/param'; +export * from './modules/base/controller/admin/open'; +export * from './modules/base/controller/admin/sys/department'; +export * from './modules/base/controller/admin/sys/log'; +export * from './modules/base/controller/admin/sys/menu'; +export * from './modules/base/controller/admin/sys/param'; +export * from './modules/base/controller/admin/sys/role'; +export * from './modules/base/controller/admin/sys/user'; +export * from './modules/base/controller/app/comm'; +export * from './modules/base/event/menu'; +export * from './modules/base/job/log'; +export * from './modules/demo/config'; +export * from './modules/demo/controller/open/plugin'; +export * from './modules/dict/config'; +export * from './modules/dict/service/info'; +export * from './modules/dict/controller/admin/info'; +export * from './modules/dict/service/type'; +export * from './modules/dict/controller/admin/type'; +export * from './modules/dict/controller/app/info'; +export * from './modules/plugin/config'; +export * from './modules/plugin/controller/admin/info'; +export * from './modules/plugin/event/app'; +export * from './modules/plugin/hooks/base'; +export * from './modules/plugin/hooks/upload/interface'; +export * from './modules/plugin/hooks/upload/index'; +export * from './modules/recycle/config'; +export * from './modules/recycle/service/data'; +export * from './modules/recycle/controller/admin/data'; +export * from './modules/recycle/event/data'; +export * from './modules/recycle/schedule/data'; +export * from './modules/space/config'; +export * from './modules/space/service/info'; +export * from './modules/space/controller/admin/info'; +export * from './modules/space/service/type'; +export * from './modules/space/controller/admin/type'; +export * from './modules/task/service/bull'; +export * from './modules/task/queue/task'; +export * from './modules/task/service/local'; +export * from './modules/task/service/info'; +export * from './modules/task/middleware/task'; +export * from './modules/task/config'; +export * from './modules/task/controller/admin/info'; +export * from './modules/task/event/app'; +export * from './modules/task/service/demo'; +export * from './modules/user/middleware/app'; +export * from './modules/user/config'; +export * from './modules/user/service/address'; +export * from './modules/user/controller/admin/address'; +export * from './modules/user/controller/admin/info'; +export * from './modules/user/controller/app/address'; +export * from './modules/user/service/wx'; +export * from './modules/user/controller/app/comm'; +export * from './modules/user/service/sms'; +export * from './modules/user/service/info'; +export * from './modules/user/controller/app/info'; +export * from './modules/user/service/login'; +export * from './modules/user/controller/app/login'; +export * from './modules/user/event/app'; diff --git a/src/modules/base/db.json b/src/modules/base/db.json index 81ffd6f..285598a 100644 --- a/src/modules/base/db.json +++ b/src/modules/base/db.json @@ -86,7 +86,6 @@ "password": "e10adc3949ba59abbe56e057f20f883e", "passwordV": 7, "nickName": "管理员", - "headImg": "https://cool-js.com/admin/headimg.jpg", "phone": "18000000000", "email": "team@cool-js.com", "status": 1, diff --git a/src/modules/demo/config.ts b/src/modules/demo/config.ts new file mode 100644 index 0000000..a0f978c --- /dev/null +++ b/src/modules/demo/config.ts @@ -0,0 +1,19 @@ +import { ModuleConfig } from '@cool-midway/core'; + +/** + * 模块配置 + */ +export default () => { + return { + // 模块名称 + name: 'demo模块', + // 模块描述 + description: '演示用', + // 中间件,只对本模块有效 + middlewares: [], + // 中间件,全局有效 + globalMiddlewares: [], + // 模块加载顺序,默认为0,值越大越优先加载 + order: 0, + } as ModuleConfig; +}; diff --git a/src/modules/demo/controller/open/plugin.ts b/src/modules/demo/controller/open/plugin.ts new file mode 100644 index 0000000..ed381d7 --- /dev/null +++ b/src/modules/demo/controller/open/plugin.ts @@ -0,0 +1,25 @@ +import { CoolController, BaseController } from '@cool-midway/core'; +import { PluginService } from '../../../plugin/service/info'; +import { Get, Inject } from '@midwayjs/core'; + +/** + * 插件 + */ +@CoolController() +export class OpenDemoPluginController extends BaseController { + @Inject() + pluginService: PluginService; + + @Get('/invoke', { summary: '调用插件' }) + async invoke() { + // 获取插件实例 + const instance = await this.pluginService.getInstance('feishu'); + instance.sendByHook({ + msg_type: 'text', + content: { + text: '测试', + }, + }); + return this.ok(); + } +} diff --git a/src/modules/plugin/entity/info.ts b/src/modules/plugin/entity/info.ts index 2c75d57..3a7eaeb 100644 --- a/src/modules/plugin/entity/info.ts +++ b/src/modules/plugin/entity/info.ts @@ -36,6 +36,18 @@ export class PluginInfoEntity extends BaseEntity { @Column({ comment: '状态 0-禁用 1-启用', default: 0 }) status: number; + @Column({ comment: '内容', type: 'json' }) + content: { + type: 'comm' | 'module'; + data: string; + }; + + @Column({ comment: 'ts内容', type: 'json' }) + tsContent: { + type: 'ts'; + data: string; + }; + @Column({ comment: '插件的plugin.json', type: 'json', nullable: true }) pluginJson: any; diff --git a/src/modules/plugin/hooks/upload/index.ts b/src/modules/plugin/hooks/upload/index.ts index 2c277e2..a45b798 100644 --- a/src/modules/plugin/hooks/upload/index.ts +++ b/src/modules/plugin/hooks/upload/index.ts @@ -6,17 +6,7 @@ import * as moment from 'moment'; import { v1 as uuid } from 'uuid'; import { CoolCommException } from '@cool-midway/core'; import * as _ from 'lodash'; -import * as os from 'os'; -import * as pkg from '../../../../../package.json'; - -// 获得上传目录 -export const getUploadDir = () => { - const uploadDir = path.join(os.homedir(), `.${pkg.name}`, 'upload'); - if (!fs.existsSync(uploadDir)) { - fs.mkdirSync(uploadDir, { recursive: true }); - } - return uploadDir; -}; +import { pUploadPath } from '../../../../comm/path'; /** * 文件上传 @@ -56,7 +46,7 @@ export class CoolPlugin extends BasePluginHook implements BaseUpload { ? await download(url) : fs.readFileSync(url); // 创建文件夹 - const dirPath = path.join(getUploadDir(), `${moment().format('YYYYMMDD')}`); + const dirPath = path.join(pUploadPath(), `${moment().format('YYYYMMDD')}`); if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); } @@ -95,7 +85,7 @@ export class CoolPlugin extends BasePluginHook implements BaseUpload { if (_.isEmpty(ctx.files)) { throw new CoolCommException('上传文件为空'); } - const basePath = getUploadDir(); + const basePath = pUploadPath(); const file = ctx.files[0]; const extension = file.filename.split('.').pop(); diff --git a/src/modules/plugin/service/info.ts b/src/modules/plugin/service/info.ts index dd85475..09eaec9 100644 --- a/src/modules/plugin/service/info.ts +++ b/src/modules/plugin/service/info.ts @@ -29,6 +29,7 @@ import { PluginMap, AnyString } from '../../../../typings/plugin'; import { PluginTypesService } from './types'; import * as path from 'path'; import * as fs from 'fs'; +import { pPluginPath } from '../../../comm/path'; /** * 插件信息 */ @@ -254,38 +255,42 @@ export class PluginService extends BaseService { tsContent: string; errorData: string; }> { - const decompress = require('decompress'); - const files = await decompress(filePath); + const AdmZip = require('adm-zip'); + const zip = new AdmZip(filePath); + const files = zip.getEntries(); let errorData; let pluginJson: PluginInfo, readme: string, logo: string, content: string, tsContent: string; + try { + // 通用方法获取文件内容 + const getFileContent = ( + entryName: string, + encoding: 'utf-8' | 'base64' = 'utf-8' + ) => { + const file = _.find(files, { entryName }); + if (!file) { + throw new Error(`File ${entryName} not found`); + } + return file?.getData()?.toString(encoding); + }; + errorData = 'plugin.json'; - pluginJson = JSON.parse( - _.find(files, { path: 'plugin.json', type: 'file' }).data.toString() - ); + pluginJson = JSON.parse(getFileContent('plugin.json')); + errorData = 'readme'; - readme = _.find(files, { - path: pluginJson.readme, - type: 'file', - }).data.toString(); + readme = getFileContent(pluginJson.readme); + errorData = 'logo'; - logo = _.find(files, { - path: pluginJson.logo, - type: 'file', - }).data.toString('base64'); - content = _.find(files, { - path: 'src/index.js', - type: 'file', - }).data.toString(); - tsContent = - _.find(files, { - path: 'source/index.ts', - type: 'file', - })?.data?.toString() || ''; + logo = getFileContent(pluginJson.logo, 'base64'); + + errorData = 'content'; + content = getFileContent('src/index.js'); + + tsContent = getFileContent('source/index.ts'); } catch (e) { throw new CoolCommException('插件信息不完整'); } @@ -328,6 +333,14 @@ export class PluginService extends BaseService { hook: pluginJson.hook, readme, logo, + content: { + type: 'comm', + data: content, + }, + tsContent: { + type: 'ts', + data: tsContent, + }, description: pluginJson.description, pluginJson, config: pluginJson.config, @@ -411,10 +424,30 @@ export class PluginService extends BaseService { }> { const filePath = this.pluginPath(keyName); if (!fs.existsSync(filePath)) { - this.logger.warn( - `插件[${keyName}]文件不存在,请卸载后重新安装: ${filePath}` - ); - return null; + // 尝试从数据库中获取 + const info = await this.pluginInfoEntity.findOne({ + where: { keyName: Equal(keyName) }, + select: ['content', 'tsContent'], + }); + if (info) { + // 保存插件到文件 + this.saveData( + { + content: info.content, + tsContent: info.tsContent, + }, + keyName + ); + return { + content: info.content, + tsContent: info.tsContent, + }; + } else { + this.logger.warn( + `插件[${keyName}]文件不存在,请卸载后重新安装: ${filePath}` + ); + return; + } } return JSON.parse(await fs.promises.readFile(filePath, 'utf-8')); } @@ -436,12 +469,6 @@ export class PluginService extends BaseService { * @returns */ pluginPath(keyName: string) { - return path.join( - this.app.getBaseDir(), - '..', - 'cool', - 'plugin', - `${keyName}.cool` - ); + return path.join(pPluginPath(), `${keyName}`); } } diff --git a/src/modules/task/config.ts b/src/modules/task/config.ts new file mode 100644 index 0000000..ff47e1f --- /dev/null +++ b/src/modules/task/config.ts @@ -0,0 +1,23 @@ +import { ModuleConfig } from '@cool-midway/core'; +import { TaskMiddleware } from './middleware/task'; + +/** + * 模块配置 + */ +export default () => { + return { + // 模块名称 + name: '任务调度', + // 模块描述 + description: '任务调度模块,支持分布式任务,由redis整个集群的任务', + // 中间件 + middlewares: [TaskMiddleware], + // 模块加载顺序,默认为0,值越大越优先加载 + order: 0, + // 日志 + log: { + // 日志保留时间,单位天 + keepDays: 20, + }, + } as ModuleConfig; +}; diff --git a/src/modules/task/controller/admin/info.ts b/src/modules/task/controller/admin/info.ts new file mode 100644 index 0000000..21f6044 --- /dev/null +++ b/src/modules/task/controller/admin/info.ts @@ -0,0 +1,59 @@ +import { Body, Get, Inject, Post, Provide, Query } from '@midwayjs/core'; +import { CoolController, BaseController } from '@cool-midway/core'; +import { TaskInfoEntity } from '../../entity/info'; +import { TaskInfoService } from '../../service/info'; + +/** + * 任务 + */ +@Provide() +@CoolController({ + api: ['add', 'delete', 'update', 'info', 'page'], + entity: TaskInfoEntity, + service: TaskInfoService, + before: ctx => { + ctx.request.body.limit = ctx.request.body.repeatCount; + }, + pageQueryOp: { + fieldEq: ['status', 'type'], + }, +}) +export class TaskInfoController extends BaseController { + @Inject() + taskInfoService: TaskInfoService; + + /** + * 手动执行一次 + */ + @Post('/once', { summary: '执行一次' }) + async once(@Body('id') id: number) { + await this.taskInfoService.once(id); + this.ok(); + } + + /** + * 暂停任务 + */ + @Post('/stop', { summary: '停止' }) + async stop(@Body('id') id: number) { + await this.taskInfoService.stop(id); + this.ok(); + } + + /** + * 开始任务 + */ + @Post('/start', { summary: '开始' }) + async start(@Body('id') id: number, @Body('type') type: number) { + await this.taskInfoService.start(id, type); + this.ok(); + } + + /** + * 日志 + */ + @Get('/log', { summary: '日志' }) + async log(@Query() params: any) { + return this.ok(await this.taskInfoService.log(params)); + } +} diff --git a/src/modules/task/db.json b/src/modules/task/db.json new file mode 100644 index 0000000..d293962 --- /dev/null +++ b/src/modules/task/db.json @@ -0,0 +1,40 @@ +{ + "task_info": [ + { + "id": 1, + "jobId": "089f554c-fdd4-4093-9f84-4cfb6af2f514", + "repeatConf": "{\"count\":1,\"type\":1,\"limit\":5,\"name\":\"每秒执行,总共5次\",\"taskType\":1,\"every\":1000,\"service\":\"taskDemoService.test()\",\"status\":1,\"id\":1,\"createTime\":\"2021-03-10 14:25:13\",\"updateTime\":\"2021-03-10 14:25:13\",\"jobId\":1}", + "name": "每秒执行一次", + "cron": null, + "limit": null, + "every": 1000, + "remark": null, + "status": 0, + "startDate": null, + "endDate": null, + "data": null, + "service": "taskDemoService.test(1,2)", + "type": 1, + "nextRunTime": "2021-3-10 14:25:18", + "taskType": 1 + }, + { + "id": 2, + "jobId": "9e1f42c8-b127-449b-b0a4-d53c60b79e75", + "repeatConf": "{\"count\":1,\"id\":2,\"createTime\":\"2021-03-10 14:25:53\",\"updateTime\":\"2021-03-10 14:25:55\",\"name\":\"cron任务,5秒执行一次\",\"cron\":\"0/5 * * * * ? \",\"status\":1,\"service\":\"taskDemoService.test()\",\"type\":1,\"nextRunTime\":\"2021-03-10 14:26:00\",\"taskType\":0,\"jobId\":2}", + "name": "cron任务,5秒执行一次", + "cron": "0/5 * * * * * ", + "limit": null, + "every": null, + "remark": null, + "status": 0, + "startDate": null, + "endDate": null, + "data": null, + "service": "taskDemoService.test()", + "type": 1, + "nextRunTime": null, + "taskType": 0 + } + ] +} \ No newline at end of file diff --git a/src/modules/task/entity/info.ts b/src/modules/task/entity/info.ts new file mode 100644 index 0000000..08c88f4 --- /dev/null +++ b/src/modules/task/entity/info.ts @@ -0,0 +1,62 @@ +import { BaseEntity } from '@cool-midway/core'; +import { Column, Entity } from 'typeorm'; + +/** + * 任务信息 + */ +@Entity('task_info') +export class TaskInfoEntity extends BaseEntity { + @Column({ comment: '任务ID', nullable: true }) + jobId: string; + + @Column({ comment: '任务配置', nullable: true, length: 1000 }) + repeatConf: string; + + @Column({ comment: '名称' }) + name: string; + + @Column({ comment: 'cron', nullable: true }) + cron: string; + + @Column({ comment: '最大执行次数 不传为无限次', nullable: true }) + limit: number; + + @Column({ + comment: '每间隔多少毫秒执行一次 如果cron设置了 这项设置就无效', + nullable: true, + }) + every: number; + + @Column({ comment: '备注', nullable: true }) + remark: string; + + @Column({ comment: '状态 0-停止 1-运行', default: 1 }) + status: number; + + @Column({ comment: '开始时间', nullable: true }) + startDate: Date; + + @Column({ comment: '结束时间', nullable: true }) + endDate: Date; + + @Column({ comment: '数据', nullable: true }) + data: string; + + @Column({ comment: '执行的service实例ID', nullable: true }) + service: string; + + @Column({ comment: '状态 0-系统 1-用户', default: 0 }) + type: number; + + @Column({ comment: '下一次执行时间', nullable: true }) + nextRunTime: Date; + + @Column({ comment: '状态 0-cron 1-时间间隔', default: 0 }) + taskType: number; + + @Column({ type: 'datetime', nullable: true }) + lastExecuteTime: Date; + + @Column({ type: 'datetime', nullable: true }) + lockExpireTime: Date; +} diff --git a/src/modules/task/entity/log.ts b/src/modules/task/entity/log.ts new file mode 100644 index 0000000..2f9030a --- /dev/null +++ b/src/modules/task/entity/log.ts @@ -0,0 +1,18 @@ +import { BaseEntity } from '@cool-midway/core'; +import { Column, Index, Entity } from 'typeorm'; + +/** + * 任务日志 + */ +@Entity('task_log') +export class TaskLogEntity extends BaseEntity { + @Index() + @Column({ comment: '任务ID', nullable: true }) + taskId: number; + + @Column({ comment: '状态 0-失败 1-成功', default: 0 }) + status: number; + + @Column({ comment: '详情描述', nullable: true, type: 'text' }) + detail: string; +} diff --git a/src/modules/task/event/app.ts b/src/modules/task/event/app.ts new file mode 100644 index 0000000..8f27601 --- /dev/null +++ b/src/modules/task/event/app.ts @@ -0,0 +1,17 @@ +import { Inject } from '@midwayjs/core'; +import { CoolEvent, Event } from '@cool-midway/core'; +import { TaskInfoService } from '../service/info'; + +/** + * 应用事件 + */ +@CoolEvent() +export class TaskAppEvent { + @Inject() + taskInfoService: TaskInfoService; + + @Event('onServerReady') + async onServerReady() { + this.taskInfoService.initTask(); + } +} diff --git a/src/modules/task/middleware/task.ts b/src/modules/task/middleware/task.ts new file mode 100644 index 0000000..f1ed76a --- /dev/null +++ b/src/modules/task/middleware/task.ts @@ -0,0 +1,38 @@ +import { CoolCommException } from '@cool-midway/core'; +import { Inject, Middleware } from '@midwayjs/core'; +import { NextFunction, Context } from '@midwayjs/koa'; +import { IMiddleware } from '@midwayjs/core'; +import { TaskInfoQueue } from '../queue/task'; +import { TaskInfoService } from '../service/info'; + +/** + * 任务中间件 + */ +@Middleware() +export class TaskMiddleware implements IMiddleware { + @Inject() + taskInfoQueue: TaskInfoQueue; + + @Inject() + taskInfoService: TaskInfoService; + + resolve() { + return async (ctx: Context, next: NextFunction) => { + const urls = ctx.url.split('/'); + const type = await this.taskInfoService.initType(); + if ( + ['add', 'update', 'once', 'stop', 'start'].includes( + urls[urls.length - 1] + ) && + type == 'bull' + ) { + if (!this.taskInfoQueue.metaQueue) { + throw new CoolCommException( + 'task插件未启用或redis配置错误或redis版本过低(>=6.x)' + ); + } + } + await next(); + }; + } +} diff --git a/src/modules/task/queue/task.ts b/src/modules/task/queue/task.ts new file mode 100644 index 0000000..7c2a171 --- /dev/null +++ b/src/modules/task/queue/task.ts @@ -0,0 +1,30 @@ +import { App, Inject } from '@midwayjs/core'; +import { BaseCoolQueue, CoolQueue } from '@cool-midway/task'; +import { TaskBullService } from '../service/bull'; +import { IMidwayApplication } from '@midwayjs/core'; + +/** + * 任务 + */ +@CoolQueue() +export abstract class TaskInfoQueue extends BaseCoolQueue { + @App() + app: IMidwayApplication; + + @Inject() + taskBullService: TaskBullService; + + async data(job, done: any): Promise { + try { + const result = await this.taskBullService.invokeService(job.data.service); + this.taskBullService.record(job.data, 1, JSON.stringify(result)); + } catch (error) { + this.taskBullService.record(job.data, 0, error.message); + } + if (!job.data.isOnce) { + this.taskBullService.updateNextRunTime(job.data.jobId); + this.taskBullService.updateStatus(job.data.id); + } + done(); + } +} diff --git a/src/modules/task/service/bull.ts b/src/modules/task/service/bull.ts new file mode 100644 index 0000000..4ffc4fb --- /dev/null +++ b/src/modules/task/service/bull.ts @@ -0,0 +1,339 @@ +import { + App, + Config, + Inject, + Logger, + Provide, + Scope, + ScopeEnum, +} from '@midwayjs/core'; +import { BaseService } from '@cool-midway/core'; +import { InjectEntityModel } from '@midwayjs/typeorm'; +import { Equal, LessThan, Repository } from 'typeorm'; +import { TaskInfoEntity } from '../entity/info'; +import { TaskLogEntity } from '../entity/log'; +import { ILogger } from '@midwayjs/logger'; +import * as _ from 'lodash'; +import { Utils } from '../../../comm/utils'; +import { TaskInfoQueue } from '../queue/task'; +import { IMidwayApplication } from '@midwayjs/core'; +import { v4 as uuidv4 } from 'uuid'; +import * as moment from 'moment'; +/** + * 任务 + */ +@Provide() +@Scope(ScopeEnum.Request, { allowDowngrade: true }) +export class TaskBullService extends BaseService { + @InjectEntityModel(TaskInfoEntity) + taskInfoEntity: Repository; + + @Logger() + logger: ILogger; + + @InjectEntityModel(TaskLogEntity) + taskLogEntity: Repository; + + @Inject() + taskInfoQueue: TaskInfoQueue; + + @App() + app: IMidwayApplication; + + @Inject() + utils: Utils; + + @Config('task.log.keepDays') + keepDays: number; + + /** + * 停止任务 + * @param id + */ + async stop(id) { + const task = await this.taskInfoEntity.findOneBy({ id: Equal(id) }); + if (task) { + const result = await this.taskInfoQueue.getJobSchedulers(); + const job = _.find(result, e => { + return e.template?.data?.jobId === task.jobId; + }); + if (job) { + await this.taskInfoQueue.removeJobScheduler(job.key); + } + task.status = 0; + await this.taskInfoEntity.update(task.id, task); + await this.updateNextRunTime(task.jobId); + } + } + /** + * 移除任务 + * @param taskId + */ + async remove(taskId) { + const info = await this.taskInfoEntity.findOneBy({ id: Equal(taskId) }); + const result = await this.taskInfoQueue.getJobSchedulers(); + const job = _.find(result, { id: info?.jobId }); + if (job) { + await this.taskInfoQueue.removeJobScheduler(job.key); + } + } + /** + * 开始任务 + * @param id + * @param type + */ + async start(id, type?) { + const task = await this.taskInfoEntity.findOneBy({ id: Equal(id) }); + task.status = 1; + if (type || type == 0) { + task.type = type; + } + await this.addOrUpdate(task); + } + /** + * 手动执行一次 + * @param id + */ + async once(id) { + const task = await this.taskInfoEntity.findOneBy({ id: Equal(id) }); + if (task) { + await this.taskInfoQueue.add( + { + ...task, + isOnce: true, + }, + { + jobId: task.jobId, + removeOnComplete: true, + removeOnFail: true, + } + ); + } + } + /** + * 检查任务是否存在 + * @param jobId + */ + async exist(jobId) { + const info = await this.taskInfoEntity.findOneBy({ jobId: Equal(jobId) }); + const result = await this.taskInfoQueue.getJobSchedulers(); + const ids = result.map(e => { + return e.id; + }); + return ids.includes(info?.jobId); + } + /** + * 新增或修改 + * @param params + */ + async addOrUpdate(params) { + delete params.repeatCount; + let repeatConf; + if (!params.jobId) { + params.jobId = uuidv4(); + } + await this.getOrmManager().transaction(async transactionalEntityManager => { + if (params.taskType === 0) { + params.limit = null; + params.every = null; + } else { + params.cron = null; + } + await transactionalEntityManager.save(TaskInfoEntity, params); + if (params.status === 1) { + const exist = await this.exist(params.id); + if (exist) { + await this.remove(params.id); + } + const { every, limit, startDate, endDate, cron } = params; + const repeat = { + every, + limit, + jobId: params.jobId, + startDate, + endDate, + cron, + }; + await this.utils.removeEmptyP(repeat); + const result = await this.taskInfoQueue.add(params, { + jobId: params.jobId, + removeOnComplete: true, + removeOnFail: true, + repeat, + }); + if (!result) { + throw new Error('任务添加失败,请检查任务配置'); + } + // await transactionalEntityManager.update(TaskInfoEntity, params.id, { + // jobId: params.id, + // type: params.type, + // }); + repeatConf = result.opts; + } + }); + if (params.status === 1) { + this.utils.sleep(1000); + await this.updateNextRunTime(params.jobId); + await this.taskInfoEntity.update(params.id, { + repeatConf: JSON.stringify(repeatConf.repeat), + }); + } + } + /** + * 删除 + * @param ids + */ + async delete(ids) { + let idArr; + if (ids instanceof Array) { + idArr = ids; + } else { + idArr = ids.split(','); + } + for (const id of idArr) { + const task = await this.taskInfoEntity.findOneBy({ id }); + const exist = await this.exist(task.id); + if (exist) { + this.stop(task.id); + } + await this.taskInfoEntity.delete({ id }); + await this.taskLogEntity.delete({ taskId: id }); + } + } + + /** + * 保存任务记录,成功任务每个任务保留最新20条日志,失败日志不会删除 + * @param task + * @param status + * @param detail + */ + async record(task, status, detail?) { + const info = await this.taskInfoEntity.findOneBy({ + jobId: Equal(task.jobId), + }); + await this.taskLogEntity.save({ + taskId: info.id, + status, + detail: detail || '', + }); + // 删除时间超过20天的日志 + await this.taskLogEntity.delete({ + taskId: info.id, + createTime: LessThan(moment().subtract(this.keepDays, 'days').toDate()), + }); + } + /** + * 初始化任务 + */ + async initTask() { + try { + await this.utils.sleep(3000); + this.logger.info('init task....'); + const runningTasks = await this.taskInfoEntity.findBy({ status: 1 }); + if (!_.isEmpty(runningTasks)) { + for (const task of runningTasks) { + const job = await this.exist(task.id); // 任务已存在就不添加 + if (!job) { + this.logger.info(`init task ${task.name}`); + await this.addOrUpdate(task); + } + } + } + } catch (e) {} + } + /** + * 任务ID + * @param jobId + */ + async getNextRunTime(jobId) { + let nextRunTime; + const result = await this.taskInfoQueue.getJobSchedulers(); + const task = _.find(result, e => { + return e.template?.data?.jobId === jobId; + }); + if (task) { + nextRunTime = new Date(task.next); + } + return nextRunTime; + } + /** + * 更新下次执行时间 + * @param jobId + */ + async updateNextRunTime(jobId) { + await this.taskInfoEntity.update( + { jobId }, + { + nextRunTime: await this.getNextRunTime(jobId), + } + ); + } + /** + * 详情 + * @param id + * @returns + */ + async info(id: any): Promise { + const info = await this.taskInfoEntity.findOneBy({ id }); + return { + ...info, + repeatCount: info.limit, + }; + } + /** + * 刷新任务状态 + */ + async updateStatus(jobId) { + const result = await this.taskInfoQueue.getJobSchedulers(); + const job = _.find(result, { id: jobId + '' }); + if (!job) { + return; + } + const task = await this.taskInfoEntity.findOneBy({ id: job.id }); + const nextTime = await this.getNextRunTime(task.jobId); + if (task) { + // if (task.nextRunTime.getTime() == nextTime.getTime()) { + // task.status = 0; + // task.nextRunTime = nextTime; + // this.taskInfoQueue.removeRepeatableByKey(job.key); + // } else { + task.nextRunTime = nextTime; + // } + await this.taskInfoEntity.update(task.id, task); + } + } + /** + * 调用service + * @param serviceStr + */ + async invokeService(serviceStr) { + if (serviceStr) { + const arr = serviceStr.split('.'); + const service = await this.app + .getApplicationContext() + .getAsync(_.lowerFirst(arr[0])); + for (let i = 1; i < arr.length; i++) { + const child = arr[i]; + if (child.includes('(')) { + const [methodName, paramsStr] = child.split('('); + const params = paramsStr + .replace(')', '') + .split(',') + .map(param => param.trim()); + if (params.length === 1 && params[0] === '') { + return service[methodName](); + } else { + const parsedParams = params.map(param => { + try { + return JSON.parse(param); + } catch (e) { + return param; // 如果不是有效的JSON,则返回原始字符串 + } + }); + return service[methodName](...parsedParams); + } + } + } + } + } +} diff --git a/src/modules/task/service/demo.ts b/src/modules/task/service/demo.ts new file mode 100644 index 0000000..8fff379 --- /dev/null +++ b/src/modules/task/service/demo.ts @@ -0,0 +1,19 @@ +import { Logger, Provide } from '@midwayjs/core'; +import { BaseService } from '@cool-midway/core'; +import { ILogger } from '@midwayjs/logger'; + +/** + * 描述 + */ +@Provide() +export class TaskDemoService extends BaseService { + @Logger() + logger: ILogger; + /** + * 描述 + */ + async test(a, b) { + this.logger.info('我被调用了', a, b); + return '任务执行成功'; + } +} diff --git a/src/modules/task/service/info.ts b/src/modules/task/service/info.ts new file mode 100644 index 0000000..b1acd64 --- /dev/null +++ b/src/modules/task/service/info.ts @@ -0,0 +1,153 @@ +import { App, Init, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core'; +import { BaseService } from '@cool-midway/core'; +import { InjectEntityModel } from '@midwayjs/typeorm'; +import { Repository } from 'typeorm'; +import { TaskInfoEntity } from '../entity/info'; +import * as _ from 'lodash'; +import { IMidwayApplication } from '@midwayjs/core'; +import { CoolQueueHandle } from '@cool-midway/task'; +import { TaskBullService } from './bull'; +import { TaskLocalService } from './local'; +import { TaskLogEntity } from '../entity/log'; +/** + * 任务 + */ +@Provide() +@Scope(ScopeEnum.Request, { allowDowngrade: true }) +export class TaskInfoService extends BaseService { + @InjectEntityModel(TaskInfoEntity) + taskInfoEntity: Repository; + + @InjectEntityModel(TaskLogEntity) + taskLogEntity: Repository; + + type: 'local' | 'bull' = 'local'; + + @App() + app: IMidwayApplication; + + @Inject() + taskBullService: TaskBullService; + + @Inject() + taskLocalService: TaskLocalService; + + @Init() + async init() { + await super.init(); + await this.initType(); + this.setEntity(this.taskInfoEntity); + } + + /** + * 初始化任务类型 + */ + async initType() { + try { + const check = await this.app + .getApplicationContext() + .getAsync(CoolQueueHandle); + if (check) { + this.type = 'bull'; + } else { + this.type = 'local'; + } + } catch (e) { + this.type = 'local'; + } + return this.type; + } + + /** + * 停止任务 + * @param id + */ + async stop(id) { + this.type === 'bull' + ? this.taskBullService.stop(id) + : this.taskLocalService.stop(id); + } + + /** + * 开始任务 + * @param id + * @param type + */ + async start(id, type?) { + this.type === 'bull' + ? this.taskBullService.start(id) + : this.taskLocalService.start(id, type); + } + /** + * 手动执行一次 + * @param id + */ + async once(id) { + this.type === 'bull' + ? this.taskBullService.once(id) + : this.taskLocalService.once(id); + } + /** + * 检查任务是否存在 + * @param jobId + */ + async exist(jobId) { + this.type === 'bull' + ? this.taskBullService.exist(jobId) + : this.taskLocalService.exist(jobId); + } + /** + * 新增或修改 + * @param params + */ + async addOrUpdate(params) { + this.type === 'bull' + ? this.taskBullService.addOrUpdate(params) + : this.taskLocalService.addOrUpdate(params); + } + /** + * 删除 + * @param ids + */ + async delete(ids) { + this.type === 'bull' + ? this.taskBullService.delete(ids) + : this.taskLocalService.delete(ids); + } + /** + * 任务日志 + * @param query + */ + async log(query) { + const { id, status } = query; + const find = await this.taskLogEntity + .createQueryBuilder('a') + .select(['a.*', 'b.name as taskName']) + .leftJoin(TaskInfoEntity, 'b', 'a.taskId = b.id') + .where('a.taskId = :id', { id }); + if (status || status == 0) { + find.andWhere('a.status = :status', { status }); + } + return await this.entityRenderPage(find, query); + } + + /** + * 初始化任务 + */ + async initTask() { + this.type === 'bull' + ? this.taskBullService.initTask() + : this.taskLocalService.initTask(); + } + + /** + * 详情 + * @param id + * @returns + */ + async info(id: any): Promise { + this.type === 'bull' + ? this.taskBullService.info(id) + : this.taskLocalService.info(id); + } +} diff --git a/src/modules/task/service/local.ts b/src/modules/task/service/local.ts new file mode 100644 index 0000000..ec94557 --- /dev/null +++ b/src/modules/task/service/local.ts @@ -0,0 +1,336 @@ +import { + App, + Config, + Inject, + Logger, + Provide, + Scope, + ScopeEnum, +} from '@midwayjs/core'; +import { BaseService } from '@cool-midway/core'; +import { InjectEntityModel } from '@midwayjs/typeorm'; +import { Equal, LessThan, Repository } from 'typeorm'; +import { TaskInfoEntity } from '../entity/info'; +import { TaskLogEntity } from '../entity/log'; +import { ILogger } from '@midwayjs/logger'; +import * as _ from 'lodash'; +import { Utils } from '../../../comm/utils'; +import { IMidwayApplication } from '@midwayjs/core'; +import { v4 as uuidv4 } from 'uuid'; +import * as moment from 'moment'; +import * as CronJob from 'cron'; + +/** + * 本地任务 + */ +@Provide() +@Scope(ScopeEnum.Singleton) +export class TaskLocalService extends BaseService { + @InjectEntityModel(TaskInfoEntity) + taskInfoEntity: Repository; + + @Logger() + logger: ILogger; + + @InjectEntityModel(TaskLogEntity) + taskLogEntity: Repository; + + @App() + app: IMidwayApplication; + + @Inject() + utils: Utils; + + @Config('task.log.keepDays') + keepDays: number; + + // 存储所有运行的任务 + private cronJobs: Map = new Map(); + + /** + * 停止任务 + */ + async stop(id) { + const task = await this.taskInfoEntity.findOneBy({ id: Equal(id) }); + if (task) { + const job = this.cronJobs.get(task.jobId); + if (job) { + job.stop(); + this.cronJobs.delete(task.jobId); + } + task.status = 0; + await this.taskInfoEntity.update(task.id, task); + await this.updateNextRunTime(task.jobId); + } + } + + /** + * 开始任务 + */ + async start(id, type?) { + const task = await this.taskInfoEntity.findOneBy({ id: Equal(id) }); + task.status = 1; + if (type || type == 0) { + task.type = type; + } + await this.addOrUpdate(task); + } + + /** + * 手动执行一次 + */ + async once(id) { + const task = await this.taskInfoEntity.findOneBy({ id: Equal(id) }); + if (task) { + await this.executeJob(task); + } + } + + /** + * 检查任务是否存在 + */ + async exist(jobId) { + return this.cronJobs.has(jobId); + } + + /** + * 创建定时任务 + */ + private createCronJob(task) { + let cronTime; + if (task.taskType === 0) { + // cron 类型 + cronTime = task.cron; + } else { + // 间隔类型 + cronTime = `*/${task.every / 1000} * * * * *`; + } + + const job = new CronJob.CronJob( + cronTime, + async () => { + await this.executeJob(task); + }, + null, + false, + 'Asia/Shanghai' + ); + + this.cronJobs.set(task.jobId, job); + job.start(); + return job; + } + + /** + * 执行任务 + */ + private async executeJob(task) { + await this.executor(task); + } + + /** + * 新增或修改 + */ + async addOrUpdate(params) { + if (!params.jobId) { + params.jobId = uuidv4(); + } + + await this.getOrmManager().transaction(async transactionalEntityManager => { + if (params.taskType === 0) { + params.limit = null; + params.every = null; + } else { + params.cron = null; + } + await transactionalEntityManager.save(TaskInfoEntity, params); + + if (params.status === 1) { + const exist = await this.exist(params.jobId); + if (exist) { + const job = this.cronJobs.get(params.jobId); + job.stop(); + this.cronJobs.delete(params.jobId); + } + this.createCronJob(params); + } + }); + + if (params.status === 1) { + await this.utils.sleep(1000); + await this.updateNextRunTime(params.jobId); + } + } + + /** + * 删除任务 + */ + async delete(ids) { + let idArr; + if (ids instanceof Array) { + idArr = ids; + } else { + idArr = ids.split(','); + } + for (const id of idArr) { + const task = await this.taskInfoEntity.findOneBy({ id }); + if (task) { + const job = this.cronJobs.get(task.jobId); + if (job) { + job.stop(); + this.cronJobs.delete(task.jobId); + } + await this.taskInfoEntity.delete({ id }); + await this.taskLogEntity.delete({ taskId: id }); + } + } + } + + /** + * 记录任务执行情况 + */ + async record(task, status, detail?) { + const info = await this.taskInfoEntity.findOneBy({ + jobId: Equal(task.jobId), + }); + await this.taskLogEntity.save({ + taskId: info.id, + status, + detail: detail || '', + }); + await this.taskLogEntity.delete({ + taskId: info.id, + createTime: LessThan(moment().subtract(this.keepDays, 'days').toDate()), + }); + } + + /** + * 获取下次执行时间 + */ + async getNextRunTime(jobId) { + const job = this.cronJobs.get(jobId); + return job ? job.nextDate().toJSDate() : null; + } + + /** + * 更新下次执行时间 + */ + async updateNextRunTime(jobId) { + const nextRunTime = await this.getNextRunTime(jobId); + if (nextRunTime) { + await this.taskInfoEntity.update({ jobId }, { nextRunTime }); + } + } + + /** + * 初始化任务 + */ + async initTask() { + try { + await this.utils.sleep(3000); + this.logger.info('init local task....'); + const runningTasks = await this.taskInfoEntity.findBy({ status: 1 }); + if (!_.isEmpty(runningTasks)) { + for (const task of runningTasks) { + const job = await this.exist(task.jobId); + if (!job) { + this.logger.info(`init local task ${task.name}`); + await this.addOrUpdate(task); + } + } + } + } catch (e) { + this.logger.error('Init local task error:', e); + } + } + + /** + * 调用service + */ + async invokeService(serviceStr) { + if (serviceStr) { + const arr = serviceStr.split('.'); + const service = await this.app + .getApplicationContext() + .getAsync(_.lowerFirst(arr[0])); + + for (let i = 1; i < arr.length; i++) { + const child = arr[i]; + if (child.includes('(')) { + const [methodName, paramsStr] = child.split('('); + const params = paramsStr + .replace(')', '') + .split(',') + .map(param => param.trim()); + + if (params.length === 1 && params[0] === '') { + return service[methodName](); + } else { + const parsedParams = params.map(param => { + try { + return JSON.parse(param); + } catch (e) { + return param; + } + }); + return service[methodName](...parsedParams); + } + } + } + } + } + + /** + * 获取任务详情 + */ + async info(id: any): Promise { + const info = await this.taskInfoEntity.findOneBy({ id }); + return { + ...info, + repeatCount: info.limit, + }; + } + + /** + * 执行器 + */ + async executor(task: any): Promise { + try { + const currentTime = moment(); + const lockExpireTime = moment().add(5, 'minutes'); + const result = await this.taskInfoEntity + .createQueryBuilder() + .update() + .set({ + lastExecuteTime: currentTime, + lockExpireTime: lockExpireTime, + }) + .where('id = :id', { id: task.id }) + .andWhere('lockExpireTime IS NULL OR lockExpireTime < :currentTime', { + currentTime, + }) + .execute(); + + // 如果更新失败(affected === 0),说明其他实例正在执行 + if (result.affected === 0) { + return; + } + + const serviceResult = await this.invokeService(task.service); + await this.record(task, 1, JSON.stringify(serviceResult)); + } catch (error) { + await this.record(task, 0, error.message); + } finally { + // 释放锁 + await this.taskInfoEntity.update( + { id: task.id }, + { lockExpireTime: null } + ); + } + + if (!task.isOnce) { + await this.updateNextRunTime(task.jobId); + await this.taskInfoEntity.update({ id: task.id }, { status: 1 }); + } + } +} diff --git a/src/modules/user/config.ts b/src/modules/user/config.ts new file mode 100644 index 0000000..d6dadf2 --- /dev/null +++ b/src/modules/user/config.ts @@ -0,0 +1,34 @@ +import { ModuleConfig } from '@cool-midway/core'; +import { UserMiddleware } from './middleware/app'; + +/** + * 模块配置 + */ +export default () => { + return { + // 模块名称 + name: '用户模块', + // 模块描述 + description: 'APP、小程序、公众号等用户', + // 中间件,只对本模块有效 + middlewares: [], + // 中间件,全局有效 + globalMiddlewares: [UserMiddleware], + // 模块加载顺序,默认为0,值越大越优先加载 + order: 0, + // 短信 + sms: { + // 验证码有效期,单位秒 + timeout: 60 * 3, + }, + // jwt + jwt: { + // token 过期时间,单位秒 + expire: 60 * 60 * 24, + // 刷新token 过期时间,单位秒 + refreshExpire: 60 * 60 * 24 * 30, + // jwt 秘钥 + secret: '52dee820-a5d9-46ed-858b-ea193c3f84e2x', + }, + } as ModuleConfig; +}; diff --git a/src/modules/user/controller/admin/address.ts b/src/modules/user/controller/admin/address.ts new file mode 100644 index 0000000..fa18ade --- /dev/null +++ b/src/modules/user/controller/admin/address.ts @@ -0,0 +1,13 @@ +import { CoolController, BaseController } from '@cool-midway/core'; +import { UserAddressEntity } from '../../entity/address'; +import { UserAddressService } from '../../service/address'; + +/** + * 用户-地址 + */ +@CoolController({ + api: ['add', 'delete', 'update', 'info', 'list', 'page'], + entity: UserAddressEntity, + service: UserAddressService, +}) +export class AdminUserAddressesController extends BaseController {} diff --git a/src/modules/user/controller/admin/info.ts b/src/modules/user/controller/admin/info.ts new file mode 100644 index 0000000..12f1480 --- /dev/null +++ b/src/modules/user/controller/admin/info.ts @@ -0,0 +1,15 @@ +import { CoolController, BaseController } from '@cool-midway/core'; +import { UserInfoEntity } from '../../entity/info'; + +/** + * 用户信息 + */ +@CoolController({ + api: ['add', 'delete', 'update', 'info', 'list', 'page'], + entity: UserInfoEntity, + pageQueryOp: { + fieldEq: ['status', 'gender', 'loginType'], + keyWordLikeFields: ['nickName', 'phone'], + }, +}) +export class AdminUserInfoController extends BaseController {} diff --git a/src/modules/user/controller/app/address.ts b/src/modules/user/controller/app/address.ts new file mode 100644 index 0000000..ae3eba5 --- /dev/null +++ b/src/modules/user/controller/app/address.ts @@ -0,0 +1,39 @@ +import { Get, Inject, Provide } from '@midwayjs/core'; +import { CoolController, BaseController } from '@cool-midway/core'; +import { UserAddressEntity } from '../../entity/address'; +import { UserAddressService } from '../../service/address'; + +/** + * 地址 + */ +@Provide() +@CoolController({ + api: ['add', 'delete', 'update', 'info', 'list', 'page'], + entity: UserAddressEntity, + service: UserAddressService, + insertParam: ctx => { + return { + userId: ctx.user.id, + }; + }, + pageQueryOp: { + where: async ctx => { + return [['userId =:userId', { userId: ctx.user.id }]]; + }, + addOrderBy: { + isDefault: 'DESC', + }, + }, +}) +export class AppUserAddressController extends BaseController { + @Inject() + userAddressService: UserAddressService; + + @Inject() + ctx; + + @Get('/default', { summary: '默认地址' }) + async default() { + return this.ok(await this.userAddressService.default(this.ctx.user.id)); + } +} diff --git a/src/modules/user/controller/app/comm.ts b/src/modules/user/controller/app/comm.ts new file mode 100644 index 0000000..be06d6c --- /dev/null +++ b/src/modules/user/controller/app/comm.ts @@ -0,0 +1,25 @@ +import { + CoolController, + BaseController, + CoolUrlTag, + TagTypes, + CoolTag, +} from '@cool-midway/core'; +import { Body, Inject, Post } from '@midwayjs/core'; +import { UserWxService } from '../../service/wx'; + +/** + * 通用 + */ +@CoolUrlTag() +@CoolController() +export class UserCommController extends BaseController { + @Inject() + userWxService: UserWxService; + + @CoolTag(TagTypes.IGNORE_TOKEN) + @Post('/wxMpConfig', { summary: '获取微信公众号配置' }) + public async getWxMpConfig(@Body('url') url: string) { + return this.ok(await this.userWxService.getWxMpConfig(url)); + } +} diff --git a/src/modules/user/controller/app/info.ts b/src/modules/user/controller/app/info.ts new file mode 100644 index 0000000..b1a94ab --- /dev/null +++ b/src/modules/user/controller/app/info.ts @@ -0,0 +1,65 @@ +import { CoolController, BaseController } from '@cool-midway/core'; +import { Body, Get, Inject, Post } from '@midwayjs/core'; +import { UserInfoService } from '../../service/info'; +import { UserInfoEntity } from '../../entity/info'; + +/** + * 用户信息 + */ +@CoolController({ + api: [], + entity: UserInfoEntity, +}) +export class AppUserInfoController extends BaseController { + @Inject() + ctx; + + @Inject() + userInfoService: UserInfoService; + + @Get('/person', { summary: '获取用户信息' }) + async person() { + return this.ok(await this.userInfoService.person(this.ctx.user.id)); + } + + @Post('/updatePerson', { summary: '更新用户信息' }) + async updatePerson(@Body() body) { + return this.ok( + await this.userInfoService.updatePerson(this.ctx.user.id, body) + ); + } + + @Post('/updatePassword', { summary: '更新用户密码' }) + async updatePassword( + @Body('password') password: string, + @Body('code') code: string + ) { + await this.userInfoService.updatePassword(this.ctx.user.id, password, code); + return this.ok(); + } + + @Post('/logoff', { summary: '注销' }) + async logoff() { + await this.userInfoService.logoff(this.ctx.user.id); + return this.ok(); + } + + @Post('/bindPhone', { summary: '绑定手机号' }) + async bindPhone(@Body('phone') phone: string, @Body('code') code: string) { + await this.userInfoService.bindPhone(this.ctx.user.id, phone, code); + return this.ok(); + } + + @Post('/miniPhone', { summary: '绑定小程序手机号' }) + async miniPhone(@Body() body) { + const { code, encryptedData, iv } = body; + return this.ok( + await this.userInfoService.miniPhone( + this.ctx.user.id, + code, + encryptedData, + iv + ) + ); + } +} diff --git a/src/modules/user/controller/app/login.ts b/src/modules/user/controller/app/login.ts new file mode 100644 index 0000000..e089df4 --- /dev/null +++ b/src/modules/user/controller/app/login.ts @@ -0,0 +1,106 @@ +import { + CoolController, + BaseController, + CoolUrlTag, + TagTypes, + CoolTag, +} from '@cool-midway/core'; +import { Body, Get, Inject, Post, Query } from '@midwayjs/core'; +import { UserLoginService } from '../../service/login'; +import { BaseSysLoginService } from '../../../base/service/sys/login'; + +/** + * 登录 + */ +@CoolUrlTag() +@CoolController() +export class AppUserLoginController extends BaseController { + @Inject() + userLoginService: UserLoginService; + + @Inject() + baseSysLoginService: BaseSysLoginService; + + @CoolTag(TagTypes.IGNORE_TOKEN) + @Post('/mini', { summary: '小程序登录' }) + async mini(@Body() body) { + const { code, encryptedData, iv } = body; + return this.ok(await this.userLoginService.mini(code, encryptedData, iv)); + } + + @CoolTag(TagTypes.IGNORE_TOKEN) + @Post('/mp', { summary: '公众号登录' }) + async mp(@Body('code') code: string) { + return this.ok(await this.userLoginService.mp(code)); + } + + @CoolTag(TagTypes.IGNORE_TOKEN) + @Post('/wxApp', { summary: '微信APP授权登录' }) + async app(@Body('code') code: string) { + return this.ok(await this.userLoginService.wxApp(code)); + } + + @CoolTag(TagTypes.IGNORE_TOKEN) + @Post('/phone', { summary: '手机号登录' }) + async phone(@Body('phone') phone: string, @Body('smsCode') smsCode: string) { + return this.ok(await this.userLoginService.phoneVerifyCode(phone, smsCode)); + } + + @CoolTag(TagTypes.IGNORE_TOKEN) + @Post('/uniPhone', { summary: '一键手机号登录' }) + async uniPhone( + @Body('access_token') access_token: string, + @Body('openid') openid: string, + @Body('appId') appId: string + ) { + return this.ok( + await this.userLoginService.uniPhone(access_token, openid, appId) + ); + } + + @CoolTag(TagTypes.IGNORE_TOKEN) + @Post('/miniPhone', { summary: '绑定小程序手机号' }) + async miniPhone(@Body() body) { + const { code, encryptedData, iv } = body; + return this.ok( + await this.userLoginService.miniPhone(code, encryptedData, iv) + ); + } + + @CoolTag(TagTypes.IGNORE_TOKEN) + @Get('/captcha', { summary: '图片验证码' }) + async captcha( + @Query('width') width: number, + @Query('height') height: number, + @Query('color') color: string + ) { + return this.ok( + await this.baseSysLoginService.captcha(width, height, color) + ); + } + + @CoolTag(TagTypes.IGNORE_TOKEN) + @Post('/smsCode', { summary: '验证码' }) + async smsCode( + @Body('phone') phone: string, + @Body('captchaId') captchaId: string, + @Body('code') code: string + ) { + return this.ok(await this.userLoginService.smsCode(phone, captchaId, code)); + } + + @CoolTag(TagTypes.IGNORE_TOKEN) + @Post('/refreshToken', { summary: '刷新token' }) + public async refreshToken(@Body('refreshToken') refreshToken) { + return this.ok(await this.userLoginService.refreshToken(refreshToken)); + } + + @CoolTag(TagTypes.IGNORE_TOKEN) + @Post('/password', { summary: '密码登录' }) + async password( + @Body('phone') phone: string, + @Body('password') password: string + ) { + return this.ok(await this.userLoginService.password(phone, password)); + } +} diff --git a/src/modules/user/entity/address.ts b/src/modules/user/entity/address.ts new file mode 100644 index 0000000..6b93391 --- /dev/null +++ b/src/modules/user/entity/address.ts @@ -0,0 +1,34 @@ +import { BaseEntity } from '@cool-midway/core'; +import { Entity, Column, Index } from 'typeorm'; + +/** + * 用户模块-收货地址 + */ +@Entity('user_address') +export class UserAddressEntity extends BaseEntity { + @Index() + @Column({ comment: '用户ID' }) + userId: number; + + @Column({ comment: '联系人' }) + contact: string; + + @Index() + @Column({ comment: '手机号', length: 11 }) + phone: string; + + @Column({ comment: '省' }) + province: string; + + @Column({ comment: '市' }) + city: string; + + @Column({ comment: '区' }) + district: string; + + @Column({ comment: '地址' }) + address: string; + + @Column({ comment: '是否默认', default: false }) + isDefault: boolean; +} diff --git a/src/modules/user/entity/info.ts b/src/modules/user/entity/info.ts new file mode 100644 index 0000000..d0bbac3 --- /dev/null +++ b/src/modules/user/entity/info.ts @@ -0,0 +1,34 @@ +import { BaseEntity } from '@cool-midway/core'; +import { Column, Entity, Index } from 'typeorm'; + +/** + * 用户信息 + */ +@Entity('user_info') +export class UserInfoEntity extends BaseEntity { + @Index({ unique: true }) + @Column({ comment: '登录唯一ID', nullable: true }) + unionid: string; + + @Column({ comment: '头像', nullable: true }) + avatarUrl: string; + + @Column({ comment: '昵称', nullable: true }) + nickName: string; + + @Index({ unique: true }) + @Column({ comment: '手机号', nullable: true }) + phone: string; + + @Column({ comment: '性别 0-未知 1-男 2-女', default: 0 }) + gender: number; + + @Column({ comment: '状态 0-禁用 1-正常 2-已注销', default: 1 }) + status: number; + + @Column({ comment: '登录方式 0-小程序 1-公众号 2-H5', default: 0 }) + loginType: number; + + @Column({ comment: '密码', nullable: true }) + password: string; +} diff --git a/src/modules/user/entity/wx.ts b/src/modules/user/entity/wx.ts new file mode 100644 index 0000000..ca0ae59 --- /dev/null +++ b/src/modules/user/entity/wx.ts @@ -0,0 +1,40 @@ +import { BaseEntity } from '@cool-midway/core'; +import { Column, Entity, Index } from 'typeorm'; + +/** + * 微信用户 + */ +@Entity('user_wx') +export class UserWxEntity extends BaseEntity { + @Index() + @Column({ comment: '微信unionid', nullable: true }) + unionid: string; + + @Index() + @Column({ comment: '微信openid' }) + openid: string; + + @Column({ comment: '头像', nullable: true }) + avatarUrl: string; + + @Column({ comment: '昵称', nullable: true }) + nickName: string; + + @Column({ comment: '性别 0-未知 1-男 2-女', default: 0 }) + gender: number; + + @Column({ comment: '语言', nullable: true }) + language: string; + + @Column({ comment: '城市', nullable: true }) + city: string; + + @Column({ comment: '省份', nullable: true }) + province: string; + + @Column({ comment: '国家', nullable: true }) + country: string; + + @Column({ comment: '类型 0-小程序 1-公众号 2-H5 3-APP', default: 0 }) + type: number; +} diff --git a/src/modules/user/event/app.ts b/src/modules/user/event/app.ts new file mode 100644 index 0000000..d7861f6 --- /dev/null +++ b/src/modules/user/event/app.ts @@ -0,0 +1,56 @@ +import { CoolEvent, Event } from '@cool-midway/core'; +import { App, Config, ILogger, Logger } from '@midwayjs/core'; +import { IMidwayKoaApplication } from '@midwayjs/koa'; +import * as fs from 'fs'; +import * as path from 'path'; +import { v1 as uuid } from 'uuid'; + +/** + * 修改jwt.secret + */ +@CoolEvent() +export class UserAppEvent { + @Logger() + coreLogger: ILogger; + + @Config('module') + config; + + @App() + app: IMidwayKoaApplication; + + @Event('onMenuInit') + async onMenuInit() { + if (this.app.getEnv() != 'local') return; + this.checkConfig(); + } + + /** + * 检查配置 + */ + async checkConfig() { + if (this.config.user.jwt.secret == 'cool-app-xxxxxx') { + this.coreLogger.warn( + '\x1B[36m 检测到模块[user] jwt.secret 配置是默认值,请不要关闭!即将自动修改... \x1B[0m' + ); + setTimeout(() => { + const filePath = path.join( + this.app.getBaseDir(), + '..', + 'src', + 'modules', + 'user', + 'config.ts' + ); + // 替换文件内容 + let fileData = fs.readFileSync(filePath, 'utf8'); + const secret = uuid().replace(/-/g, ''); + this.config.user.jwt.secret = secret; + fs.writeFileSync(filePath, fileData.replace('cool-app-xxxxxx', secret)); + this.coreLogger.info( + '\x1B[36m [cool:module:user] midwayjs cool module user auto modify jwt.secret\x1B[0m' + ); + }, 6000); + } + } +} diff --git a/src/modules/user/middleware/app.ts b/src/modules/user/middleware/app.ts new file mode 100644 index 0000000..b803a6f --- /dev/null +++ b/src/modules/user/middleware/app.ts @@ -0,0 +1,96 @@ +import { ALL, Config, Middleware } from '@midwayjs/core'; +import { NextFunction, Context } from '@midwayjs/koa'; +import { IMiddleware, Init, Inject } from '@midwayjs/core'; +import * as jwt from 'jsonwebtoken'; +import * as _ from 'lodash'; +import { CoolUrlTagData, RESCODE, TagTypes } from '@cool-midway/core'; + +/** + * 用户 + */ +@Middleware() +export class UserMiddleware implements IMiddleware { + @Config(ALL) + coolConfig; + + @Inject() + coolUrlTagData: CoolUrlTagData; + + @Config('module.user.jwt') + jwtConfig; + + ignoreUrls: string[] = []; + + @Config('koa.globalPrefix') + prefix; + + @Init() + async init() { + this.ignoreUrls = this.coolUrlTagData.byKey(TagTypes.IGNORE_TOKEN, 'app'); + } + + resolve() { + return async (ctx: Context, next: NextFunction) => { + let { url } = ctx; + url = url.replace(this.prefix, '').split('?')[0]; + if (_.startsWith(url, '/app/')) { + const token = ctx.get('Authorization'); + try { + ctx.user = jwt.verify(token, this.jwtConfig.secret); + if (ctx.user.isRefresh) { + ctx.status = 401; + ctx.body = { + code: RESCODE.COMMFAIL, + message: '登录失效~', + }; + return; + } + } catch (error) {} + // 使用matchUrl方法来检查URL是否应该被忽略 + const isIgnored = this.ignoreUrls.some(pattern => + this.matchUrl(pattern, url) + ); + if (isIgnored) { + await next(); + return; + } else { + if (!ctx.user) { + ctx.status = 401; + ctx.body = { + code: RESCODE.COMMFAIL, + message: '登录失效~', + }; + return; + } + } + } + await next(); + }; + } + + // 匹配URL的方法 + matchUrl(pattern, url) { + const patternSegments = pattern.split('/').filter(Boolean); + const urlSegments = url.split('/').filter(Boolean); + + // 如果段的数量不同,则无法匹配 + if (patternSegments.length !== urlSegments.length) { + return false; + } + + // 逐段进行匹配 + for (let i = 0; i < patternSegments.length; i++) { + if (patternSegments[i].startsWith(':')) { + // 如果模式段以':'开始,我们认为它是一个参数,可以匹配任何内容 + continue; + } + // 如果两个段不相同,则不匹配 + if (patternSegments[i] !== urlSegments[i]) { + return false; + } + } + + // 所有段都匹配 + return true; + } +} diff --git a/src/modules/user/service/address.ts b/src/modules/user/service/address.ts new file mode 100644 index 0000000..a478640 --- /dev/null +++ b/src/modules/user/service/address.ts @@ -0,0 +1,63 @@ +import { Init, Inject, Provide } from '@midwayjs/core'; +import { BaseService } from '@cool-midway/core'; +import { Equal, Repository } from 'typeorm'; +import { UserAddressEntity } from '../entity/address'; +import { InjectEntityModel } from '@midwayjs/typeorm'; + +/** + * 地址 + */ +@Provide() +export class UserAddressService extends BaseService { + @InjectEntityModel(UserAddressEntity) + userAddressEntity: Repository; + + @Inject() + ctx; + + @Init() + async init() { + await super.init(); + this.setEntity(this.userAddressEntity); + } + + /** + * 列表信息 + */ + async list() { + return this.userAddressEntity + .createQueryBuilder() + .where('userId = :userId ', { userId: this.ctx.user.id }) + .addOrderBy('isDefault', 'DESC') + .getMany(); + } + + /** + * 修改之后 + * @param data + * @param type + */ + async modifyAfter(data: any, type: 'add' | 'update' | 'delete') { + if (type == 'add' || type == 'update') { + if (data.isDefault) { + await this.userAddressEntity + .createQueryBuilder() + .update() + .set({ isDefault: false }) + .where('userId = :userId ', { userId: this.ctx.user.id }) + .andWhere('id != :id', { id: data.id }) + .execute(); + } + } + } + + /** + * 默认地址 + */ + async default(userId) { + return await this.userAddressEntity.findOneBy({ + userId: Equal(userId), + isDefault: true, + }); + } +} diff --git a/src/modules/user/service/info.ts b/src/modules/user/service/info.ts new file mode 100644 index 0000000..62f2017 --- /dev/null +++ b/src/modules/user/service/info.ts @@ -0,0 +1,124 @@ +import { BaseService, CoolCommException } from '@cool-midway/core'; +import { Inject, Provide } from '@midwayjs/core'; +import { InjectEntityModel } from '@midwayjs/typeorm'; +import * as md5 from 'md5'; +import { Equal, Repository } from 'typeorm'; +import { v1 as uuid } from 'uuid'; +import { PluginService } from '../../plugin/service/info'; +import { UserInfoEntity } from '../entity/info'; +import { UserSmsService } from './sms'; +import { UserWxService } from './wx'; + +/** + * 用户信息 + */ +@Provide() +export class UserInfoService extends BaseService { + @InjectEntityModel(UserInfoEntity) + userInfoEntity: Repository; + + @Inject() + pluginService: PluginService; + + @Inject() + userSmsService: UserSmsService; + + @Inject() + userWxService: UserWxService; + + /** + * 绑定小程序手机号 + * @param userId + * @param code + * @param encryptedData + * @param iv + */ + async miniPhone(userId: number, code: any, encryptedData: any, iv: any) { + const phone = await this.userWxService.miniPhone(code, encryptedData, iv); + await this.userInfoEntity.update({ id: Equal(userId) }, { phone }); + return phone; + } + + /** + * 获取用户信息 + * @param id + * @returns + */ + async person(id) { + const info = await this.userInfoEntity.findOneBy({ id: Equal(id) }); + delete info.password; + return info; + } + + /** + * 注销 + * @param userId + */ + async logoff(userId: number) { + await this.userInfoEntity.update( + { id: userId }, + { + status: 2, + phone: null, + unionid: null, + nickName: `已注销-00${userId}`, + avatarUrl: null, + } + ); + } + + /** + * 更新用户信息 + * @param id + * @param param + * @returns + */ + async updatePerson(id, param) { + const info = await this.person(id); + if (!info) throw new CoolCommException('用户不存在'); + try { + // 修改了头像要重新处理 + if (param.avatarUrl && info.avatarUrl != param.avatarUrl) { + const file = await this.pluginService.getInstance('upload'); + param.avatarUrl = await file.downAndUpload( + param.avatarUrl, + uuid() + '.png' + ); + } + } catch (err) {} + try { + return await this.userInfoEntity.update({ id }, param); + } catch (err) { + throw new CoolCommException('更新失败,参数错误或者手机号已存在'); + } + } + + /** + * 更新密码 + * @param userId + * @param password + * @param 验证码 + */ + async updatePassword(userId, password, code) { + const user = await this.userInfoEntity.findOneBy({ id: userId }); + const check = await this.userSmsService.checkCode(user.phone, code); + if (!check) { + throw new CoolCommException('验证码错误'); + } + await this.userInfoEntity.update(user.id, { password: md5(password) }); + } + + /** + * 绑定手机号 + * @param userId + * @param phone + * @param code + */ + async bindPhone(userId, phone, code) { + const check = await this.userSmsService.checkCode(phone, code); + if (!check) { + throw new CoolCommException('验证码错误'); + } + await this.userInfoEntity.update({ id: userId }, { phone }); + } +} diff --git a/src/modules/user/service/login.ts b/src/modules/user/service/login.ts new file mode 100644 index 0000000..b7708b7 --- /dev/null +++ b/src/modules/user/service/login.ts @@ -0,0 +1,307 @@ +import { Config, Inject, Provide } from '@midwayjs/core'; +import { BaseService, CoolCommException } from '@cool-midway/core'; +import { InjectEntityModel } from '@midwayjs/typeorm'; +import { Equal, Repository } from 'typeorm'; +import { UserInfoEntity } from '../entity/info'; +import { UserWxService } from './wx'; +import * as jwt from 'jsonwebtoken'; +import { UserWxEntity } from '../entity/wx'; +import { BaseSysLoginService } from '../../base/service/sys/login'; +import { UserSmsService } from './sms'; +import { v1 as uuid } from 'uuid'; +import * as md5 from 'md5'; +import { PluginService } from '../../plugin/service/info'; + +/** + * 登录 + */ +@Provide() +export class UserLoginService extends BaseService { + @InjectEntityModel(UserInfoEntity) + userInfoEntity: Repository; + + @InjectEntityModel(UserWxEntity) + userWxEntity: Repository; + + @Inject() + userWxService: UserWxService; + + @Config('module.user.jwt') + jwtConfig; + + @Inject() + baseSysLoginService: BaseSysLoginService; + + @Inject() + pluginService: PluginService; + + @Inject() + userSmsService: UserSmsService; + + /** + * 发送手机验证码 + * @param phone + * @param captchaId + * @param code + */ + async smsCode(phone, captchaId, code) { + // 1、检查图片验证码 2、发送短信验证码 + const check = await this.baseSysLoginService.captchaCheck(captchaId, code); + if (!check) { + throw new CoolCommException('图片验证码错误'); + } + await this.userSmsService.sendSms(phone); + } + + /** + * 手机验证码登录 + * @param phone + * @param smsCode + */ + async phoneVerifyCode(phone, smsCode) { + // 1、检查短信验证码 2、登录 + const check = await this.userSmsService.checkCode(phone, smsCode); + if (check) { + return await this.phone(phone); + } else { + throw new CoolCommException('验证码错误'); + } + } + + /** + * 小程序手机号登录 + * @param code + * @param encryptedData + * @param iv + */ + async miniPhone(code, encryptedData, iv) { + const phone = await this.userWxService.miniPhone(code, encryptedData, iv); + if (phone) { + return await this.phone(phone); + } else { + throw new CoolCommException('获得手机号失败,请检查配置'); + } + } + + /** + * 手机号一键登录 + * @param access_token + * @param openid + */ + async uniPhone(access_token, openid, appId) { + const instance: any = await this.pluginService.getInstance('uniphone'); + const phone = await instance.getPhone(access_token, openid, appId); + if (phone) { + return await this.phone(phone); + } else { + throw new CoolCommException('获得手机号失败,请检查配置'); + } + } + + /** + * 手机登录 + * @param phone + * @returns + */ + async phone(phone: string) { + let user: any = await this.userInfoEntity.findOneBy({ + phone: Equal(phone), + }); + if (!user) { + user = { + phone, + unionid: phone, + loginType: 2, + nickName: phone.replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2'), + }; + await this.userInfoEntity.insert(user); + } + return this.token({ id: user.id }); + } + + /** + * 公众号登录 + * @param code + */ + async mp(code: string) { + let wxUserInfo = await this.userWxService.mpUserInfo(code); + if (wxUserInfo) { + delete wxUserInfo.privilege; + wxUserInfo = await this.saveWxInfo( + { + openid: wxUserInfo.openid, + unionid: wxUserInfo.unionid, + avatarUrl: wxUserInfo.headimgurl, + nickName: wxUserInfo.nickname, + gender: wxUserInfo.sex, + city: wxUserInfo.city, + province: wxUserInfo.province, + country: wxUserInfo.country, + }, + 1 + ); + return this.wxLoginToken(wxUserInfo); + } else { + throw new Error('微信登录失败'); + } + } + + /** + * 微信APP授权登录 + * @param code + */ + async wxApp(code: string) { + let wxUserInfo = await this.userWxService.appUserInfo(code); + if (wxUserInfo) { + delete wxUserInfo.privilege; + wxUserInfo = await this.saveWxInfo( + { + openid: wxUserInfo.openid, + unionid: wxUserInfo.unionid, + avatarUrl: wxUserInfo.headimgurl, + nickName: wxUserInfo.nickname, + gender: wxUserInfo.sex, + city: wxUserInfo.city, + province: wxUserInfo.province, + country: wxUserInfo.country, + }, + 1 + ); + return this.wxLoginToken(wxUserInfo); + } else { + throw new Error('微信登录失败'); + } + } + + /** + * 保存微信信息 + * @param wxUserInfo + * @param type + * @returns + */ + async saveWxInfo(wxUserInfo, type) { + const find: any = { openid: wxUserInfo.openid }; + let wxInfo: any = await this.userWxEntity.findOneBy(find); + if (wxInfo) { + wxUserInfo.id = wxInfo.id; + } + await this.userWxEntity.save({ + ...wxUserInfo, + type, + }); + return wxUserInfo; + } + + /** + * 小程序登录 + * @param code + * @param encryptedData + * @param iv + */ + async mini(code, encryptedData, iv) { + let wxUserInfo = await this.userWxService.miniUserInfo( + code, + encryptedData, + iv + ); + if (wxUserInfo) { + // 保存 + wxUserInfo = await this.saveWxInfo(wxUserInfo, 0); + return await this.wxLoginToken(wxUserInfo); + } + } + + /** + * 微信登录 获得token + * @param wxUserInfo 微信用户信息 + * @returns + */ + async wxLoginToken(wxUserInfo) { + const unionid = wxUserInfo.unionid ? wxUserInfo.unionid : wxUserInfo.openid; + let userInfo: any = await this.userInfoEntity.findOneBy({ unionid }); + if (!userInfo) { + const file = await this.pluginService.getInstance('upload'); + const avatarUrl = await file.downAndUpload( + wxUserInfo.avatarUrl, + uuid() + '.png' + ); + userInfo = { + unionid, + nickName: wxUserInfo.nickName, + avatarUrl, + gender: wxUserInfo.gender, + }; + await this.userInfoEntity.insert(userInfo); + } + return this.token({ id: userInfo.id }); + } + + /** + * 刷新token + * @param refreshToken + */ + async refreshToken(refreshToken) { + try { + const info = jwt.verify(refreshToken, this.jwtConfig.secret); + if (!info['isRefresh']) { + throw new CoolCommException('token类型非refreshToken'); + } + const userInfo = await this.userInfoEntity.findOneBy({ + id: info['id'], + }); + return this.token({ id: userInfo.id }); + } catch (e) { + throw new CoolCommException( + '刷新token失败,请检查refreshToken是否正确或过期' + ); + } + } + + /** + * 密码登录 + * @param phone + * @param password + */ + async password(phone, password) { + const user = await this.userInfoEntity.findOneBy({ phone }); + + if (user && user.password == md5(password)) { + return this.token({ + id: user.id, + }); + } else { + throw new CoolCommException('账号或密码错误'); + } + } + + /** + * 获得token + * @param info + * @returns + */ + async token(info) { + const { expire, refreshExpire } = this.jwtConfig; + return { + expire, + token: await this.generateToken(info), + refreshExpire, + refreshToken: await this.generateToken(info, true), + }; + } + + /** + * 生成token + * @param tokenInfo 信息 + * @param roleIds 角色集合 + */ + async generateToken(info, isRefresh = false) { + const { expire, refreshExpire, secret } = this.jwtConfig; + const tokenInfo = { + isRefresh, + ...info, + }; + return jwt.sign(tokenInfo, secret, { + expiresIn: isRefresh ? refreshExpire : expire, + }); + } +} diff --git a/src/modules/user/service/sms.ts b/src/modules/user/service/sms.ts new file mode 100644 index 0000000..b1b90fa --- /dev/null +++ b/src/modules/user/service/sms.ts @@ -0,0 +1,79 @@ +import { Provide, Config, Inject, Init, InjectClient } from '@midwayjs/core'; +import { BaseService, CoolCommException } from '@cool-midway/core'; +import * as _ from 'lodash'; +import { CachingFactory, MidwayCache } from '@midwayjs/cache-manager'; +import { PluginService } from '../../plugin/service/info'; + +/** + * 描述 + */ +@Provide() +export class UserSmsService extends BaseService { + // 获得模块的配置信息 + @Config('module.user.sms') + config; + + @InjectClient(CachingFactory, 'default') + midwayCache: MidwayCache; + + @Inject() + pluginService: PluginService; + + plugin; + + @Init() + async init() { + for (const key of ['sms-tx', 'sms-ali']) { + try { + this.plugin = await this.pluginService.getInstance(key); + if (this.plugin) { + this.config.pluginKey = key; + break; + } + } catch (e) { + continue; + } + } + } + + /** + * 发送验证码 + * @param phone + */ + async sendSms(phone) { + // 随机四位验证码 + const code = _.random(1000, 9999); + const pluginKey = this.config.pluginKey; + if (!this.plugin) + throw new CoolCommException( + '未配置短信插件,请到插件市场下载安装配置:https://cool-js.com/plugin?keyWord=短信' + ); + try { + if (pluginKey == 'sms-tx') { + await this.plugin.send([phone], [code]); + } + if (pluginKey == 'sms-ali') { + await this.plugin.send([phone], { + code, + }); + } + this.midwayCache.set(`sms:${phone}`, code, this.config.timeout * 1000); + } catch (error) { + throw new CoolCommException('发送过于频繁,请稍后再试'); + } + } + + /** + * 验证验证码 + * @param phone + * @param code + * @returns + */ + async checkCode(phone, code) { + const cacheCode = await this.midwayCache.get(`sms:${phone}`); + if (code && cacheCode == code) { + return true; + } + return false; + } +} diff --git a/src/modules/user/service/wx.ts b/src/modules/user/service/wx.ts new file mode 100644 index 0000000..76ed2d0 --- /dev/null +++ b/src/modules/user/service/wx.ts @@ -0,0 +1,281 @@ +import { BaseService, CoolCommException } from '@cool-midway/core'; +import { Config, Inject, Provide } from '@midwayjs/core'; +import { InjectEntityModel } from '@midwayjs/typeorm'; +import axios from 'axios'; +import * as crypto from 'crypto'; +import * as moment from 'moment'; +import { Equal, Repository } from 'typeorm'; +import { v1 as uuid } from 'uuid'; +import { PluginService } from '../../plugin/service/info'; +import { UserInfoEntity } from '../entity/info'; +import { UserWxEntity } from '../entity/wx'; + +/** + * 微信 + */ +@Provide() +export class UserWxService extends BaseService { + @Config('module.user') + config; + + @InjectEntityModel(UserInfoEntity) + userInfoEntity: Repository; + + @InjectEntityModel(UserWxEntity) + userWxEntity: Repository; + + @Inject() + pluginService: PluginService; + + /** + * 获得插件实例 + * @returns + */ + async getPlugin() { + try { + const wxPlugin: any = await this.pluginService.getInstance('wx'); + return wxPlugin; + } catch (error) { + throw new CoolCommException( + '未配置微信插件,请到插件市场下载安装配置:https://cool-js.com/plugin/70' + ); + } + } + + /** + * 获得小程序实例 + * @returns + */ + async getMiniApp() { + const wxPlugin: any = await this.getPlugin(); + return wxPlugin.MiniApp(); + } + + /** + * 获得公众号实例 + * @returns + */ + async getOfficialAccount() { + const wxPlugin: any = await this.getPlugin(); + return wxPlugin.OfficialAccount(); + } + + /** + * 获得App实例 + * @returns + */ + async getOpenPlatform() { + const wxPlugin: any = await this.getPlugin(); + return wxPlugin.OpenPlatform(); + } + + /** + * 获得用户的openId + * @param userId + * @param type 0-小程序 1-公众号 2-App + */ + async getOpenid(userId: number, type = 0) { + const user = await this.userInfoEntity.findOneBy({ + id: Equal(userId), + status: 1, + }); + if (!user) { + throw new CoolCommException('用户不存在或已被禁用'); + } + const wx = await this.userWxEntity + .createQueryBuilder('a') + .where('a.type = :type', { type }) + .andWhere('(a.unionid = :unionid or a.openid =:openid )', { + unionid: user.unionid, + openid: user.unionid, + }) + .getOne(); + return wx ? wx.openid : null; + } + + /** + * 获得微信配置 + * @param appId + * @param appSecret + * @param url 当前网页的URL,不包含#及其后面部分(必须是调用JS接口页面的完整URL) + */ + public async getWxMpConfig(url: string) { + const token = await this.getWxToken(); + const ticket = await axios.get( + 'https://api.weixin.qq.com/cgi-bin/ticket/getticket', + { + params: { + access_token: token, + type: 'jsapi', + }, + } + ); + + const account = (await this.getOfficialAccount()).getAccount(); + const appid = account.getAppId(); + // 返回结果集 + const result = { + timestamp: parseInt(moment().valueOf() / 1000 + ''), + nonceStr: uuid(), + appId: appid, //appid + signature: '', + }; + const signArr = []; + signArr.push('jsapi_ticket=' + ticket.data.ticket); + signArr.push('noncestr=' + result.nonceStr); + signArr.push('timestamp=' + result.timestamp); + signArr.push('url=' + decodeURI(url)); + // 敏感信息加密处理 + result.signature = crypto + .createHash('sha1') + .update(signArr.join('&')) + .digest('hex') + .toUpperCase(); + return result; + } + + /** + * 获得公众号用户信息 + * @param code + */ + async mpUserInfo(code) { + const token = await this.openOrMpToken(code, 'mp'); + return await this.openOrMpUserInfo(token); + } + + /** + * 获得app用户信息 + * @param code + */ + async appUserInfo(code) { + const token = await this.openOrMpToken(code, 'open'); + return await this.openOrMpUserInfo(token); + } + + /** + * 获得微信token 不用code + * @param appid + * @param secret + */ + public async getWxToken(type = 'mp') { + let app; + if (type == 'mp') { + app = await this.getOfficialAccount(); + } else { + app = await this.getOpenPlatform(); + } + return await app.getAccessToken().getToken(); + } + + /** + * 获得用户信息 + * @param token + */ + async openOrMpUserInfo(token) { + return await axios + .get('https://api.weixin.qq.com/sns/userinfo', { + params: { + access_token: token.access_token, + openid: token.openid, + lang: 'zh_CN', + }, + }) + .then(res => { + return res.data; + }); + } + + /** + * 获得token嗯 + * @param code + * @param type + */ + async openOrMpToken(code, type = 'mp') { + const account = + type == 'mp' + ? (await this.getOfficialAccount()).getAccount() + : (await this.getMiniApp()).getAccount(); + const result = await axios.get( + 'https://api.weixin.qq.com/sns/oauth2/access_token', + { + params: { + appid: account.getAppId(), + secret: account.getSecret(), + code, + grant_type: 'authorization_code', + }, + } + ); + return result.data; + } + + /** + * 获得小程序session + * @param code 微信code + * @param conf 配置 + */ + async miniSession(code) { + const app = await this.getMiniApp(); + const utils = app.getUtils(); + const result = await utils.codeToSession(code); + return result; + } + + /** + * 获得小程序用户信息 + * @param code + * @param encryptedData + * @param iv + */ + async miniUserInfo(code, encryptedData, iv) { + const session = await this.miniSession(code); + if (session.errcode) { + throw new CoolCommException('登录失败,请重试'); + } + const info: any = await this.miniDecryptData( + encryptedData, + iv, + session.session_key + ); + if (info) { + delete info['watermark']; + return { + ...info, + openid: session['openid'], + unionid: session['unionid'], + }; + } + return null; + } + + /** + * 获得小程序手机 + * @param code + * @param encryptedData + * @param iv + */ + async miniPhone(code, encryptedData, iv) { + const session = await this.miniSession(code); + if (session.errcode) { + throw new CoolCommException('获取手机号失败,请刷新重试'); + } + const result = await this.miniDecryptData( + encryptedData, + iv, + session.session_key + ); + return result.phoneNumber; + } + + /** + * 小程序信息解密 + * @param encryptedData + * @param iv + * @param sessionKey + */ + async miniDecryptData(encryptedData, iv, sessionKey) { + const app = await this.getMiniApp(); + const utils = app.getUtils(); + return await utils.decryptSession(sessionKey, iv, encryptedData); + } +} diff --git a/typings/feishu.d.ts b/typings/feishu.d.ts new file mode 100644 index 0000000..5d914d2 --- /dev/null +++ b/typings/feishu.d.ts @@ -0,0 +1,96 @@ +import { BasePlugin } from '@cool-midway/plugin-cli'; +import axios from 'axios'; +interface Message { + /** 类型 */ + msg_type: + | 'text' + | 'post' + | 'image' + | 'file' + | 'audio' + | 'media' + | 'sticker' + | 'interactive' + | 'share_chat' + | 'share_user'; + /** 内容 */ + content: any; +} +/** + * 飞书 + */ +export declare class CoolPlugin extends BasePlugin { + /** + * 推送webHook消息 + * @param message + * @returns + */ + sendByHook(message: Message): Promise>; + /** + * 推送应用消息 + * @param message 消息 + * @param options receive_id 接收人 receive_id_type 接收人类型 message_id 消息ID uuid 消息唯一ID + * @returns + */ + sendByApp( + message: Message, + options: { + receive_id: string; + receive_id_type: 'open_id' | 'user_id' | 'chat_id' | 'union_id' | 'email'; + message_id?: string; + uuid?: string; + }, + ): Promise>; + /** + * 上传图片 + * @param filePath 文件路径 + * @param image_type message 用于发送消息 avatar 用于设置头像 + * @returns + */ + uploadImage( + filePath: string, + image_type?: 'message' | 'avatar', + ): Promise; + /** + * 上传文件 + * @param filePath 文件路径 + * @param file_type 文件类型 + * @returns + */ + uploadFile( + filePath: any, + file_type?: 'opus' | 'mp4' | 'pdf' | 'doc' | 'xls' | 'ppt' | 'stream', + ): Promise; + /** + * 获得聊天列表,如:应用所在的群组 + * @returns + */ + chatList(): Promise; + /** + *获得用户信息 + * @param options emails 邮箱数组,mobiles 手机号数组 不能同时为空 include_resigned 是否包含离职员工 + * @returns + */ + getUserInfos(options: { + emails?: string[]; + mobiles?: string[]; + include_resigned?: boolean; + }): Promise; + /** + * 使用手机号或邮箱获取用户 ID + * @param options emails 邮箱数组,mobiles 手机号数组 不能同时为空 include_resigned 是否包含离职员工 + * @returns + */ + getUserIds(options: { + emails?: string[]; + mobiles?: string[]; + include_resigned?: boolean; + }): Promise; + /** + * 获得token + * @returns + */ + getToken(): Promise; +} +export declare const Plugin: typeof CoolPlugin; +export {}; diff --git a/typings/plugin.d.ts b/typings/plugin.d.ts index e2ae5a0..99f7ab6 100644 --- a/typings/plugin.d.ts +++ b/typings/plugin.d.ts @@ -1,3 +1,4 @@ +import * as feishu from './feishu'; import { BaseUpload, MODETYPE } from './upload'; type AnyString = string & {}; /** @@ -5,4 +6,5 @@ type AnyString = string & {}; */ interface PluginMap { upload: BaseUpload; + feishu: feishu.CoolPlugin; }