gemercheung 3 years ago
parent
commit
74e700f53a

+ 14 - 14
config.yaml

@@ -3,25 +3,25 @@ http:
   port: 6688
 
 
-grpc:
-  url: '221.4.210.172:23000'
 # grpc:
-#   url: '192.168.0.47:3000'
-
-redis:
-  port: 26379
-  host: '221.4.210.172' #远程调试需要设置bindip 为0.0.0.0 并且设置密码
-  password: '' # 非远程不需要密码
-  decode_responses: true
-  db: 9
+#   url: '221.4.210.172:23000'
+grpc:
+  url: '192.168.0.200:3000'
 
 # redis:
-#   port: 6379
-#   host: '192.168.0.47' #远程调试需要设置bindip 为0.0.0.0 并且设置密码
+#   port: 26379
+#   host: '221.4.210.172' #远程调试需要设置bindip 为0.0.0.0 并且设置密码
 #   password: '' # 非远程不需要密码
 #   decode_responses: true
 #   db: 9
 
+redis:
+  port: 6379
+  host: '192.168.0.47' #远程调试需要设置bindip 为0.0.0.0 并且设置密码
+  password: '' # 非远程不需要密码
+  decode_responses: true
+  db: 9
+
 stun:
   server: ['stun:172.18.156.41:3478', 'stun:120.24.252.95:3478']
   portRangeBegin: 52000
@@ -31,9 +31,9 @@ server:
   private_ip: 172.18.197.114
   public_ip: 120.24.252.95
 
-
+  
 # PRIVATE_IP=172.18.197.114
 # PUBLIC_IP=120.24.252.95
 # STUNS_SEVER="stun:172.18.156.41:3478,stun:120.24.252.95:3478"
 # GRPC_URL="192.168.0.48:3000"
-# REDIS_URL="redis://:192.168.0.47:6379/9"
+# REDIS_URL="redis://:192.168.0.47:6379/9"

+ 3 - 0
package.json

@@ -23,6 +23,7 @@
   "dependencies": {
     "@grpc/grpc-js": "^1.6.7",
     "@grpc/proto-loader": "^0.6.12",
+    "@nestjs/bull": "^0.5.5",
     "@nestjs/common": "^8.0.0",
     "@nestjs/config": "^2.0.0",
     "@nestjs/core": "^8.0.0",
@@ -33,6 +34,7 @@
     "@nestjs/websockets": "^8.4.4",
     "ajv": "^8.11.0",
     "buffer": "^6.0.3",
+    "bull": "^4.8.3",
     "js-yaml": "^4.1.0",
     "multistream": "^4.1.0",
     "nestjs-redis": "git+https://github.com/GyanendroKh/nestjs-redis.git#nest8-fix",
@@ -47,6 +49,7 @@
     "@nestjs/cli": "^8.0.0",
     "@nestjs/schematics": "^8.0.0",
     "@nestjs/testing": "^8.0.0",
+    "@types/bull": "^3.15.8",
     "@types/express": "^4.17.13",
     "@types/jest": "27.4.1",
     "@types/js-yaml": "^4.0.5",

+ 10 - 1
src/app.module.ts

@@ -7,15 +7,24 @@ import { RoomModule } from './room/room.module';
 import { SceneModule } from './scene/scene.module';
 // import { CacheModule } from './cache/cache.module';
 import configuration from './config/configuration';
+import { BullModule } from '@nestjs/bull';
+import { RotateService } from './rotate/rotate.service';
 
 @Module({
   imports: [
     ConfigModule.forRoot({ isGlobal: true, load: [configuration] }),
     RoomModule,
     SceneModule,
+    BullModule.forRoot({
+      redis: {
+        host: 'localhost',
+        port: 6379,
+        password: 'redis9394',
+      },
+    }),
     // CacheModule,
   ],
   controllers: [AppController],
-  providers: [AppService, MetaGateway],
+  providers: [AppService, MetaGateway, RotateService],
 })
 export class AppModule {}

+ 1 - 1
src/meta.gateway.ts

@@ -187,7 +187,7 @@ export class MetaGateway
       console.log('channel is open');
       this.sceneService.handleDataChanelOpen(this.gameChanel);
       const peers = this.peer.getSelectedCandidatePair();
-      this.logger.log('配对成功', peers);
+      this.logger.log('配对成功', JSON.stringify(peers));
 
       if (this.gameChanel.isOpen()) {
         console.log('gameChanel', this.gameChanel.isOpen());

+ 18 - 0
src/rotate/rotate.service.spec.ts

@@ -0,0 +1,18 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { RotateService } from './rotate.service';
+
+describe('RotateService', () => {
+  let service: RotateService;
+
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      providers: [RotateService],
+    }).compile();
+
+    service = module.get<RotateService>(RotateService);
+  });
+
+  it('should be defined', () => {
+    expect(service).toBeDefined();
+  });
+});

+ 221 - 0
src/rotate/rotate.service.ts

@@ -0,0 +1,221 @@
+import { Injectable } from '@nestjs/common';
+import { CacheService } from 'src/cache/cache.service';
+
+@Injectable()
+export class RotateService {
+  constructor(private cacheService: CacheService) {}
+  private actionRequestPool = {};
+  private Actions = {
+    Clicking: 1,
+    Rotation: 1014,
+    Joystick: 15,
+  };
+  private users = {};
+  private reply = {
+    traceIds: [],
+    vehicle: null,
+    mediaSrc: null,
+    newUserStates: [
+      {
+        userId: 'dcff36ae4fc1d',
+        playerState: {
+          roomTypeId: '',
+          person: 0,
+          avatarId: '',
+          skinId: '',
+          roomId: '',
+          isHost: false,
+          isFollowHost: false,
+          skinDataVersion: '',
+          avatarComponents: '',
+          nickName: '',
+          movingMode: 0,
+          attitude: '',
+          areaName: '',
+          pathName: '',
+          pathId: '',
+          avatarSize: 1,
+          extra: '',
+          prioritySync: false,
+          player: {
+            position: { x: -700, y: 0, z: 0 },
+            angle: {
+              pitch: 0,
+              yaw: 0,
+              roll: 0,
+            },
+          },
+          camera: {
+            position: { x: -1145, y: 0, z: 160 },
+            angle: {
+              pitch: 0,
+              yaw: 0,
+              roll: 0,
+            },
+          },
+          cameraCenter: { x: -700, y: 0, z: 0 },
+        },
+        renderInfo: {
+          renderType: 0,
+          videoFrame: null,
+          cameraStateType: 3,
+          isMoving: 1,
+          needIfr: 0,
+          isVideo: 0,
+          stillFrame: 0,
+          isRotating: 0,
+          isFollowing: 0,
+          clientPanoTitlesBitmap: [],
+          clientPanoTreceId: '',
+          prefetchVideoId: '',
+          noMedia: false,
+        },
+        event: null,
+        relation: 1,
+      },
+    ],
+    actionResponses: [
+      // {
+      //     "actionType": 15,
+      //     "pointType": 100,
+      //     "extra": "",
+      //     "traceId": "d0864cd0-378d-4d49-b7b0-3e8e1b9494c3",
+      //     "packetId": "d44bd2f5-f877-4dd7-868b-803c64f99082",
+      //     "nps": [],
+      //     "peopleNum": 0,
+      //     "zoneId": "",
+      //     "echoMsg": "",
+      //     "reserveDetail": null,
+      //     "userWithAvatarList": [],
+      //     "newUserStates": [],
+      //     "code": 0,
+      //     "msg": ""
+      // }
+    ],
+    getStateType: 0,
+    code: 0,
+    msg: 'OK',
+  };
+
+  init(app_id, userId) {
+    const user = {
+      appId: null,
+      userId: null,
+      breakPointId: null,
+      player: {
+        position: { x: -700, y: 0, z: 0 },
+        angle: {
+          pitch: 0,
+          yaw: 0,
+          roll: 0,
+        },
+      },
+      camera: {
+        position: { x: -1145, y: 0, z: 160 },
+        angle: {
+          pitch: 0,
+          yaw: 0,
+          roll: 0,
+        },
+      },
+      rotateInfo: {
+        frameIndex: 0,
+        horizontal_move: 0,
+        mediaSrc: null,
+      },
+      moveInfo: {},
+      traceIds: [],
+    };
+    user.appId = app_id;
+    user.userId = userId;
+    user.breakPointId = 100;
+    this.users[userId] = user;
+    this.reply['newUserStates'][0]['userId'] = userId;
+    return this.reply;
+  }
+
+  async rotate(actionRequest) {
+    try {
+      const userId = actionRequest['user_id'];
+      if (this.actionRequestPool[userId]) {
+        this.actionRequestPool[userId].push(actionRequest);
+      } else {
+        this.actionRequestPool[userId] = [];
+        this.actionRequestPool[userId].push(actionRequest);
+      }
+
+      const actionRequests = this.actionRequestPool[userId];
+      const user = this.users[userId];
+      // debugger;
+      let horizontal_move = user.rotateInfo.horizontal_move;
+      const traceIds = user.traceIds;
+      let sub = 0;
+      for (let i = 0; i < actionRequests.length; ++i) {
+        if (actionRequests[i].action_type == this.Actions.Rotation) {
+          horizontal_move += actionRequests[i].rotation_action.horizontal_move;
+          traceIds.push(actionRequests[i].trace_id);
+          ++sub;
+        } else {
+          break;
+        }
+      }
+
+      actionRequests.splice(0, sub);
+      const hAngle = horizontal_move * 90;
+      if (Math.abs(hAngle) < 1) {
+        user.rotateInfo.horizontal_move = horizontal_move;
+        user.traceIds = traceIds;
+      } else {
+        user.rotateInfo.frameIndex += Math.floor(hAngle);
+      }
+
+      if (user.rotateInfo.frameIndex < 0) {
+        user.rotateInfo.frameIndex = 360 - user.rotateInfo.frameIndex;
+      } else if (user.rotateInfo.frameIndex > 359) {
+        user.rotateInfo.frameIndex -= 360;
+      }
+
+      this.reply['newUserStates'][0]['userId'] = userId;
+      //从redis里取
+      //let key = user.appId + "-"+user.breakPointId+"-"+user.rotateInfo.frameIndex;
+      const key =
+        'rotateframe:app_id:' +
+        user.appId +
+        ':frame_index:' +
+        user.rotateInfo.frameIndex +
+        ':break_point_id:' +
+        user.breakPointId;
+      // const value = null;
+
+      const redisData = await this.cacheService.get(key);
+      const value = JSON.parse(redisData);
+      console.log('redis', value);
+      user.camera['position'] = value ? value.cameraPosition : '';
+      user.camera['angle'] = value ? value.cameraAngle : '';
+
+      //this.reply['newUserStates'][0]['playerState'] .player
+      this.reply['newUserStates'][0]['playerState'].camera.position =
+        user.camera['position'];
+      this.reply['newUserStates'][0]['playerState'].camera.angle =
+        user.camera['angle'];
+      this.reply['newUserStates'][0]['playerState'].cameraCenter =
+        user.player.position;
+      // debugger
+      this.reply.mediaSrc =
+        '/' +
+        '0000000001' +
+        '/' +
+        user.breakPointId +
+        '/' +
+        value.directory +
+        '/' +
+        value.fileName +
+        '?m=' +
+        new Date().getTime();
+
+      return this.reply;
+    } catch (error) {
+      console.log('RotateService', error);
+    }
+  }
+}

+ 24 - 0
src/scene/rotate-consumer.ts

@@ -0,0 +1,24 @@
+import { OnQueueActive, Process, Processor } from '@nestjs/bull';
+import { Job } from 'bull';
+
+@Processor('rotate')
+export class RotateConsumer {
+  @Process()
+  async processFrame(job: Job<unknown>) {
+    // const progress = 0;
+    // for (i = 0; i < 100; i++) {
+    // b);
+    console.log('job', job);
+    debugger;
+
+    // }
+    return {};
+  }
+
+  @OnQueueActive()
+  onActive(job: Job) {
+    console.log(
+      `Processing job ${job.id} of type ${job.name} with data ${job.data}...`,
+    );
+  }
+}

+ 2 - 0
src/scene/scene.d.ts

@@ -71,6 +71,7 @@ interface RotateRequest {
   rotation_action?: RotationActionType;
   trace_id: string;
   user_id: string;
+  sampleRate?: number;
 }
 
 interface MoveRequest {
@@ -171,6 +172,7 @@ interface RotationActionType {
 interface RTCMessageRequest {
   action_type: number;
   echo_msg?: RTCEchoMessage;
+  MstType?: number;
   getNewUserStateAction?: newUserStateType;
   rotation_action?: RotationActionType;
   sampleRate?: number;

+ 26 - 5
src/scene/scene.module.ts

@@ -1,14 +1,35 @@
-import { Module } from '@nestjs/common';
+import { Module, OnModuleInit } from '@nestjs/common';
 import { SceneService } from './scene.service';
 import { CacheModule } from '../cache/cache.module';
 import { CacheService } from '../cache/cache.service';
 // import { RedisService } from '../redis/redis.service';
 import { StreamService } from './stream/stream.service';
+import { BullModule } from '@nestjs/bull';
+import { RotateConsumer } from './rotate-consumer';
+import { RotateService } from '../rotate/rotate.service';
 
 @Module({
-  imports: [CacheModule],
+  imports: [
+    CacheModule,
+    BullModule.registerQueue({
+      name: 'rotate',
+    }),
+  ],
   controllers: [],
-  providers: [SceneService, CacheService, StreamService],
-  exports: [SceneService],
+  providers: [
+    SceneService,
+    CacheService,
+    StreamService,
+    RotateService,
+    RotateConsumer,
+  ],
+  exports: [SceneService, CacheService],
 })
-export class SceneModule {}
+export class SceneModule implements OnModuleInit {
+  onModuleInit() {
+    // BullModule.registerQueue({
+    //   configKey: 'rotate-queue',
+    //   name: 'rotate',
+    // });
+  }
+}

+ 118 - 71
src/scene/scene.service.ts

@@ -11,6 +11,9 @@ import { BehaviorSubject } from 'rxjs';
 import { ActionType } from './actionType';
 import { CacheService } from 'src/cache/cache.service';
 import { StreamService } from './stream/stream.service';
+import { InjectQueue } from '@nestjs/bull';
+import { Queue } from 'bull';
+import { RotateService } from 'src/rotate/rotate.service';
 
 const frameMetaReply = {
   traceIds: [''],
@@ -76,6 +79,8 @@ export class SceneService implements OnModuleInit, OnModuleDestroy {
   constructor(
     private cacheService: CacheService,
     private streamService: StreamService,
+    private rotateService: RotateService,
+    @InjectQueue('rotate') private rotateQueue: Queue,
   ) {}
   @Client(grpcClientOptions) private readonly client: ClientGrpc;
   private sceneGrpcService: SceneGrpcService;
@@ -92,6 +97,8 @@ export class SceneService implements OnModuleInit, OnModuleDestroy {
   private testFrame = -1;
   private RotateframeCnt = -1;
   private mockserverTime = Date.now() - 1653000000478;
+  private lastRenderMedia = '';
+  // private rotateMap = new Map()<>;
 
   setConfig(user_id: string, roomId: string) {
     this.user_id = user_id;
@@ -135,6 +142,7 @@ export class SceneService implements OnModuleInit, OnModuleDestroy {
       // initReply.subscribe((reply) => {
       //   console.log('initReply', reply);
       // });
+      this.rotateService.init(request.app_id, request.user_id);
     } catch (error) {
       console.log('error', error);
     }
@@ -151,45 +159,52 @@ export class SceneService implements OnModuleInit, OnModuleDestroy {
     return this.sceneGrpcService.move(request);
   }
 
-  async rotate1(request: RotateRequest) {
+  async rotate(request: RotateRequest) {
     try {
       // const reply = this.sceneGrpcService.rotate(request);
       if (!this.onSteaming) {
         this.RotateframeCnt = this.frameCnt;
-        const redisMeta = await this.cacheService.rpop(
-          `updateFrameMetadata:${this.user_id}`,
-        );
-        console.log('rotate信息', this.user_id, request);
+        // const redisMeta = await this.cacheService.get(
+        //   `updateFrameMetadata:${this.user_id}`,
+        // );
+        // console.log('rotate信息', this.user_id, request.sampleRate);
+
+        const redisMeta = await this.rotateService.rotate(request);
+        console.log('rotate信息', redisMeta);
+        // await this.rotateQueue.add('processFrame', request, {
+        //   jobId: request.trace_id,
+        // });
         // console.log('rotate.user_id', this.user_id, redisMeta);
-        if (redisMeta && redisMeta.length > 0) {
-          const meta = JSON.parse(redisMeta);
-          const mediaSrc: string = meta.mediaSrc || '';
+        if (redisMeta && 'mediaSrc' in redisMeta) {
+          // const meta = JSON.parse(redisMeta);
+          const mediaSrc: string = redisMeta.mediaSrc || '';
 
-          console.log('meta', meta);
-          console.log('mediaSrc', mediaSrc);
+          // console.log('meta', meta);
+          // console.log('mediaSrc', mediaSrc);
 
           if (mediaSrc.length > 0) {
-            this.frameCnt += 1;
-            // this.onSteaming = true;
-            const src = meta.mediaSrc.split('?')[0];
-            const testclipPath = src.replace(
-              '/mnt/oss/metaverse/scene/0000000001/100/',
-              '',
-            );
-            console.log('testclipPath', testclipPath);
-            const demoPath = path.join(
-              __dirname,
-              `../ws/video/${testclipPath}`,
-            );
-            delete meta.mediaSrc;
-            const stream: StreamFrameType = {
-              frame: this.frameCnt,
-              clipPath: demoPath,
-              metaData: JSON.stringify(meta),
-              serverTime: this.mockserverTime,
-              DIR: 3,
-            };
-            this.streamService.pushFrameToSteam(stream);
+            let src = mediaSrc.split('?')[0];
+            // 临时本地替换路经
+            src = src.replace('/0000000001/100/', '');
+            // 判断不是同一条源时才推出
+            console.log('[media]', this.lastRenderMedia, src);
+            if (this.lastRenderMedia !== src) {
+              console.log('不同源');
+              this.frameCnt += 1;
+              console.log('src', src);
+              this.lastRenderMedia = src;
+              const clipPath = path.join(__dirname, `../ws/video/${src}`);
+              console.log('src-clipPath', src, clipPath);
+              delete redisMeta.mediaSrc;
+              const stream: StreamFrameType = {
+                frame: this.frameCnt,
+                clipPath: clipPath,
+                metaData: JSON.stringify(redisMeta),
+                serverTime: this.mockserverTime,
+                DIR: 3,
+              };
+              this.streamService.pushFrameToSteam(stream);
+            }
           }
         }
       }
@@ -203,7 +218,7 @@ export class SceneService implements OnModuleInit, OnModuleDestroy {
     }
   }
 
-  async rotate(request: RotateRequest) {
+  async rotate1(request: RotateRequest) {
     try {
       // const reply = this.sceneGrpcService.rotate(request);
       // const res = await this.cacheService.publish(
@@ -217,8 +232,8 @@ export class SceneService implements OnModuleInit, OnModuleDestroy {
         this.frameCnt += 1;
         this.RotateframeCnt = this.frameCnt;
         // this.cacheService
-        this.testFrame += 1;
-        this.mockserverTime += 10;
+        this.testFrame += 3;
+        this.mockserverTime += 1;
         this.onSteaming = true;
         if (this.testFrame > 358) this.testFrame = 0;
         const stream: StreamFrameType = {
@@ -242,7 +257,10 @@ export class SceneService implements OnModuleInit, OnModuleDestroy {
       //   }
       // });
     } catch (error) {
-      this.logger.error('rotate', error);
+      this.logger.error(
+        `rotate-${this.frameCnt},src:${this.testFrame}`,
+        JSON.stringify(error),
+      );
     }
   }
 
@@ -272,34 +290,57 @@ export class SceneService implements OnModuleInit, OnModuleDestroy {
   handleMessage(message: string | Buffer) {
     try {
       if (typeof message === 'string') {
-        const msg: RTCMessageRequest = JSON.parse(message);
-        switch (msg.action_type) {
-          case ActionType.walk:
-            const walk = msg;
-            this.walking(walk);
-            break;
-          case ActionType.breathPoint:
-            this.handleBreath(msg);
-            break;
-          case ActionType.rotate:
-            const rotateRequest: RotateRequest = msg;
-            this.rotate(rotateRequest);
-            break;
-          case ActionType.userStatus:
-            this.updateUserStatus(msg);
-            break;
-          case ActionType.status:
-            this.updateStatus();
-            break;
-          default:
-            break;
+        // wasm:特例, requestIframe
+        if (message.includes('wasm:')) {
+          const msg: RTCMessageRequest = JSON.parse(
+            message.replace('wasm:', ''),
+          );
+          if (msg.MstType === 0) {
+            this.logger.log('lost I frame');
+            this.handleIframeRequest();
+          }
+        } else {
+          const msg: RTCMessageRequest = JSON.parse(message);
+          switch (msg.action_type) {
+            case ActionType.walk:
+              const walk = msg;
+              this.walking(walk);
+              break;
+            case ActionType.breathPoint:
+              this.handleBreath(msg);
+              break;
+            case ActionType.rotate:
+              const rotateRequest: RotateRequest = msg;
+              this.rotate(rotateRequest);
+              break;
+            case ActionType.userStatus:
+              this.updateUserStatus(msg);
+              break;
+            case ActionType.status:
+              this.updateStatus();
+              break;
+            default:
+              break;
+          }
         }
       }
     } catch (error) {
-      this.logger.error('handleMessage:rtc', error);
+      this.logger.error('handleMessage:rtc--error', message);
     }
   }
 
+  handleIframeRequest() {
+    this.frameCnt += 1;
+    this.onSteaming = true;
+    const stream: StreamFrameType = {
+      frame: this.frameCnt,
+      clipPath: path.join(__dirname, '../ws/video/100/100.0000.h264'),
+      metaData: JSON.stringify(frameMetaReply),
+      serverTime: this.mockserverTime,
+    };
+    this.streamService.pushFrameToSteam(stream);
+  }
+
   walking(request) {
     console.log('walking', request);
     console.log('walking-onSteaming', this.onSteaming);
@@ -599,20 +640,26 @@ export class SceneService implements OnModuleInit, OnModuleDestroy {
       code: 0,
       msg: '',
     };
-    // const redisMeta = await this.cacheService.rpop(
-    //   `updateFrameMetadata:${this.user_id}`,
-    // );
+    const redisMeta = await this.cacheService.get(
+      `updateFrameMetadata:${this.user_id}`,
+    );
     //TODO 接入redis数据
-    // console.log('redisMeta', redisMeta);
-    // if (redisMeta && redisMeta.length > 0) {
-    //   const meta = JSON.parse(redisMeta);
-    //   'mediaSrc' in meta && delete meta.mediaSrc;
-    //   meta.action_type = 1024;
-    //   this.streamService.pushNormalDataToStream(meta);
-    // } else {
-    //   this.streamService.pushNormalDataToStream(reply);
-    // }
-    this.streamService.pushNormalDataToStream(reply);
+    console.log(
+      'redisMeta',
+      redisMeta && redisMeta.length,
+      `updateFrameMetadata:${this.user_id}`,
+    );
+
+    if (redisMeta && redisMeta.length > 0) {
+      console.log('updateUserStatus-真数据', redisMeta && redisMeta.length);
+      const meta = JSON.parse(redisMeta);
+      'mediaSrc' in meta && delete meta.mediaSrc;
+      meta.action_type = 1024;
+      this.streamService.pushNormalDataToStream(redisMeta);
+    } else {
+      this.streamService.pushNormalDataToStream(reply);
+    }
+    // this.streamService.pushNormalDataToStream(reply);
   }
   handleStartCountingFrame() {
     this._frameInteval = setInterval(async () => {
@@ -624,7 +671,7 @@ export class SceneService implements OnModuleInit, OnModuleDestroy {
             frame: 1,
             clipPath: path.join(__dirname, '../ws/video/100/100.0000.h264'),
             metaData: JSON.stringify(frameMetaReply),
-            serverTime: 754871824,
+            serverTime: this.mockserverTime,
           };
           this.streamService.pushFrameToSteam(stream);
         }

+ 3 - 4
src/scene/stream/stream.service.ts

@@ -162,10 +162,9 @@ export class StreamService {
       steam.on('end', () => {
         steamByteLength = 0;
         console.log('stream end');
-        this.onSteaming.next(false);
-        // if(this.onSteaming){
-        //   setTimeout()
-        // }
+        if (this.onSteaming) {
+          this.onSteaming.next(false);
+        }
       });
     } catch (error) {
       this.logger.error(error);

+ 138 - 13
yarn.lock

@@ -583,6 +583,51 @@
     "@jridgewell/resolve-uri" "^3.0.3"
     "@jridgewell/sourcemap-codec" "^1.4.10"
 
+"@msgpackr-extract/msgpackr-extract-darwin-arm64@2.0.2":
+  version "2.0.2"
+  resolved "https://registry.npmmirror.com/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-2.0.2.tgz#01e3669b8b2dc01f6353f2c87e1ec94faf52c587"
+  integrity sha512-FMX5i7a+ojIguHpWbzh5MCsCouJkwf4z4ejdUY/fsgB9Vkdak4ZnoIEskOyOUMMB4lctiZFGszFQJXUeFL8tRg==
+
+"@msgpackr-extract/msgpackr-extract-darwin-x64@2.0.2":
+  version "2.0.2"
+  resolved "https://registry.npmmirror.com/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-2.0.2.tgz#5ca32f16e6f1b7854001a1a2345b61d4e26a0931"
+  integrity sha512-DznYtF3lHuZDSRaIOYeif4JgO0NtO2Xf8DsngAugMx/bUdTFbg86jDTmkVJBNmV+cxszz6OjGvinnS8AbJ342g==
+
+"@msgpackr-extract/msgpackr-extract-linux-arm64@2.0.2":
+  version "2.0.2"
+  resolved "https://registry.npmmirror.com/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-2.0.2.tgz#ff629f94379981bf476dffb1439a7c1d3dba2d72"
+  integrity sha512-b0jMEo566YdM2K+BurSed7bswjo3a6bcdw5ETqoIfSuxKuRLPfAiOjVbZyZBgx3J/TAM/QrvEQ/VN89A0ZAxSg==
+
+"@msgpackr-extract/msgpackr-extract-linux-arm@2.0.2":
+  version "2.0.2"
+  resolved "https://registry.npmmirror.com/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-2.0.2.tgz#5f6fd30d266c4a90cf989049c7f2e50e5d4fcd4c"
+  integrity sha512-Gy9+c3Wj+rUlD3YvCZTi92gs+cRX7ZQogtwq0IhRenloTTlsbpezNgk6OCkt59V4ATEWSic9rbU92H/l7XsRvA==
+
+"@msgpackr-extract/msgpackr-extract-linux-x64@2.0.2":
+  version "2.0.2"
+  resolved "https://registry.npmmirror.com/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-2.0.2.tgz#167faa553b9dbffac8b03bf27de9b6f846f0e1bc"
+  integrity sha512-zrBHaePwcv4cQXxzYgNj0+A8I1uVN97E7/3LmkRocYZ+rMwUsnPpp4RuTAHSRoKlTQV3nSdCQW4Qdt4MXw/iHw==
+
+"@msgpackr-extract/msgpackr-extract-win32-x64@2.0.2":
+  version "2.0.2"
+  resolved "https://registry.npmmirror.com/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-2.0.2.tgz#baea7764b1adf201ce4a792fe971fd7211dad2e4"
+  integrity sha512-fpnI00dt+yO1cKx9qBXelKhPBdEgvc8ZPav1+0r09j0woYQU2N79w/jcGawSY5UGlgQ3vjaJsFHnGbGvvqdLzg==
+
+"@nestjs/bull-shared@^0.0.5":
+  version "0.0.5"
+  resolved "https://registry.npmmirror.com/@nestjs/bull-shared/-/bull-shared-0.0.5.tgz#5cd9907fdc99cdd2a2088406bcae086e13918c4a"
+  integrity sha512-MPkl1q7N/ZlzLq4SC/NWdZ+7pIOF9x70+92xBcUPx7J6mewqVR1gzADHzEexErZEgg+K/n5nwJGe+BLlYFTUgg==
+  dependencies:
+    tslib "2.3.1"
+
+"@nestjs/bull@^0.5.5":
+  version "0.5.5"
+  resolved "https://registry.npmmirror.com/@nestjs/bull/-/bull-0.5.5.tgz#1ee9b7d8b78a52af58c4654de8d765a2a2b16862"
+  integrity sha512-g42/KH8YJmJE60kQVzQucWm5xEfyOj8iwXp2s3Ox3Yu6B/mmzNmqmXLnACnaD15q3WtH84vOhME41CJANxbQeQ==
+  dependencies:
+    "@nestjs/bull-shared" "^0.0.5"
+    tslib "2.3.1"
+
 "@nestjs/cli@^8.0.0":
   version "8.2.5"
   resolved "https://registry.npmmirror.com/@nestjs/cli/-/cli-8.2.5.tgz"
@@ -933,6 +978,14 @@
     "@types/connect" "*"
     "@types/node" "*"
 
+"@types/bull@^3.15.8":
+  version "3.15.8"
+  resolved "https://registry.npmmirror.com/@types/bull/-/bull-3.15.8.tgz#ae2139f94490d740b37c8da5d828ce75dd82ce7c"
+  integrity sha512-8DbSPMSsZH5PWPnGEkAZLYgJEH4ghHJNKF7LB6Wr5R0/v6g+Vs+JoaA7kcvLtHE936xg2WpFPkaoaJgExOmKDw==
+  dependencies:
+    "@types/ioredis" "*"
+    "@types/redis" "^2.8.0"
+
 "@types/component-emitter@^1.2.10":
   version "1.2.11"
   resolved "https://registry.npmmirror.com/@types/component-emitter/-/component-emitter-1.2.11.tgz"
@@ -1091,6 +1144,13 @@
   resolved "https://registry.npmmirror.com/@types/range-parser/-/range-parser-1.2.4.tgz"
   integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
 
+"@types/redis@^2.8.0":
+  version "2.8.32"
+  resolved "https://registry.npmmirror.com/@types/redis/-/redis-2.8.32.tgz#1d3430219afbee10f8cfa389dad2571a05ecfb11"
+  integrity sha512-7jkMKxcGq9p242exlbsVzuJb57KqHRhNl4dHoQu2Y5v9bCAbtIXXH0R3HleSQW4CTOqpHIYUW3t6tpUj4BVQ+w==
+  dependencies:
+    "@types/node" "*"
+
 "@types/serve-static@*":
   version "1.13.10"
   resolved "https://registry.npmmirror.com/@types/serve-static/-/serve-static-1.13.10.tgz"
@@ -1437,17 +1497,7 @@ ajv@^6.10.0, ajv@^6.12.4, ajv@^6.12.5:
     json-schema-traverse "^0.4.1"
     uri-js "^4.2.2"
 
-ajv@^8.0.0:
-  version "8.11.0"
-  resolved "https://registry.npmmirror.com/ajv/-/ajv-8.11.0.tgz"
-  integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==
-  dependencies:
-    fast-deep-equal "^3.1.1"
-    json-schema-traverse "^1.0.0"
-    require-from-string "^2.0.2"
-    uri-js "^4.2.2"
-
-ajv@^8.11.0:
+ajv@^8.0.0, ajv@^8.11.0:
   version "8.11.0"
   resolved "https://registry.npmmirror.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f"
   integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==
@@ -1765,6 +1815,21 @@ buffer@^6.0.3:
     base64-js "^1.3.1"
     ieee754 "^1.2.1"
 
+bull@^4.8.3:
+  version "4.8.3"
+  resolved "https://registry.npmmirror.com/bull/-/bull-4.8.3.tgz#4ab67029fee1183dcb7185895b20dc08c02d6bf2"
+  integrity sha512-oOHr+KTLu3JM5V9TXsg18/1xyVQceoYCFiGrXZOpu9abZn3W3vXJtMBrwB6Yvl/RxSKVVBpoa25RF/ya3750qg==
+  dependencies:
+    cron-parser "^4.2.1"
+    debuglog "^1.0.0"
+    get-port "^5.1.1"
+    ioredis "^4.28.5"
+    lodash "^4.17.21"
+    msgpackr "^1.5.2"
+    p-timeout "^3.2.0"
+    semver "^7.3.2"
+    uuid "^8.3.0"
+
 busboy@^0.2.11:
   version "0.2.14"
   resolved "https://registry.npmmirror.com/busboy/-/busboy-0.2.14.tgz"
@@ -2075,6 +2140,13 @@ create-require@^1.1.0:
   resolved "https://registry.npmmirror.com/create-require/-/create-require-1.1.1.tgz"
   integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
 
+cron-parser@^4.2.1:
+  version "4.4.0"
+  resolved "https://registry.npmmirror.com/cron-parser/-/cron-parser-4.4.0.tgz#829d67f9e68eb52fa051e62de0418909f05db983"
+  integrity sha512-TrE5Un4rtJaKgmzPewh67yrER5uKM0qI9hGLDBfWb8GGRe9pn/SDkhVrdHa4z7h0SeyeNxnQnogws/H+AQANQA==
+  dependencies:
+    luxon "^1.28.0"
+
 cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
   version "7.0.3"
   resolved "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz"
@@ -2124,6 +2196,11 @@ debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, d
   dependencies:
     ms "2.1.2"
 
+debuglog@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.npmmirror.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
+  integrity sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw==
+
 decimal.js@^10.2.1:
   version "10.3.1"
   resolved "https://registry.npmmirror.com/decimal.js/-/decimal.js-10.3.1.tgz"
@@ -2882,6 +2959,11 @@ get-package-type@^0.1.0:
   resolved "https://registry.npmmirror.com/get-package-type/-/get-package-type-0.1.0.tgz"
   integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==
 
+get-port@^5.1.1:
+  version "5.1.1"
+  resolved "https://registry.npmmirror.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193"
+  integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==
+
 get-stream@^5.0.0:
   version "5.2.0"
   resolved "https://registry.npmmirror.com/get-stream/-/get-stream-5.2.0.tgz"
@@ -3152,7 +3234,7 @@ interpret@^1.0.0:
   resolved "https://registry.npmmirror.com/interpret/-/interpret-1.4.0.tgz"
   integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==
 
-ioredis@^4:
+ioredis@^4, ioredis@^4.28.5:
   version "4.28.5"
   resolved "https://registry.npmmirror.com/ioredis/-/ioredis-4.28.5.tgz#5c149e6a8d76a7f8fa8a504ffc85b7d5b6797f9f"
   integrity sha512-3GYo0GJtLqgNXj4YhrisLaNNvWSNwSS2wS4OELGfGxH8I69+XfNdnmV1AyN+ZqMh0i7eX+SWjrwFKDBDgfBC1A==
@@ -3931,6 +4013,11 @@ lru-cache@^6.0.0:
   dependencies:
     yallist "^4.0.0"
 
+luxon@^1.28.0:
+  version "1.28.0"
+  resolved "https://registry.npmmirror.com/luxon/-/luxon-1.28.0.tgz#e7f96daad3938c06a62de0fb027115d251251fbf"
+  integrity sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ==
+
 macos-release@^2.5.0:
   version "2.5.0"
   resolved "https://registry.npmmirror.com/macos-release/-/macos-release-2.5.0.tgz"
@@ -4073,6 +4160,27 @@ ms@2.1.3:
   resolved "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz"
   integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
 
+msgpackr-extract@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.npmmirror.com/msgpackr-extract/-/msgpackr-extract-2.0.2.tgz#201a8d7ade47e99b3ba277c45736b00e195d4670"
+  integrity sha512-coskCeJG2KDny23zWeu+6tNy7BLnAiOGgiwzlgdm4oeSsTpqEJJPguHIuKZcCdB7tzhZbXNYSg6jZAXkZErkJA==
+  dependencies:
+    node-gyp-build-optional-packages "5.0.2"
+  optionalDependencies:
+    "@msgpackr-extract/msgpackr-extract-darwin-arm64" "2.0.2"
+    "@msgpackr-extract/msgpackr-extract-darwin-x64" "2.0.2"
+    "@msgpackr-extract/msgpackr-extract-linux-arm" "2.0.2"
+    "@msgpackr-extract/msgpackr-extract-linux-arm64" "2.0.2"
+    "@msgpackr-extract/msgpackr-extract-linux-x64" "2.0.2"
+    "@msgpackr-extract/msgpackr-extract-win32-x64" "2.0.2"
+
+msgpackr@^1.5.2:
+  version "1.6.0"
+  resolved "https://registry.npmmirror.com/msgpackr/-/msgpackr-1.6.0.tgz#faa80298cbc7fd949175d73674c31c3ab3c0172c"
+  integrity sha512-CJs2OuaIuwpP2iLZx6vl/jfl7WqFNFrYpkp/BC1ctzCbYAACyT9lYMACstgvH4pTcBrCFk4uzOoOZj0gFP/0EA==
+  optionalDependencies:
+    msgpackr-extract "^2.0.2"
+
 multer@1.4.4:
   version "1.4.4"
   resolved "https://registry.npmmirror.com/multer/-/multer-1.4.4.tgz"
@@ -4161,6 +4269,11 @@ node-fetch@^2.6.1:
   dependencies:
     whatwg-url "^5.0.0"
 
+node-gyp-build-optional-packages@5.0.2:
+  version "5.0.2"
+  resolved "https://registry.npmmirror.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.2.tgz#3de7d30bd1f9057b5dfbaeab4a4442b7fe9c5901"
+  integrity sha512-PiN4NWmlQPqvbEFcH/omQsswWQbe5Z9YK/zdB23irp5j2XibaA2IrGvpSWmVVG4qMZdmPdwPctSy4a86rOMn6g==
+
 node-int64@^0.4.0:
   version "0.4.0"
   resolved "https://registry.npmmirror.com/node-int64/-/node-int64-0.4.0.tgz"
@@ -4308,6 +4421,11 @@ os-tmpdir@~1.0.2:
   resolved "https://registry.npmmirror.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz"
   integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==
 
+p-finally@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.npmmirror.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
+  integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==
+
 p-limit@^2.2.0:
   version "2.3.0"
   resolved "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz"
@@ -4327,6 +4445,13 @@ p-map@^2.1.0:
   resolved "https://registry.npmmirror.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175"
   integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==
 
+p-timeout@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.npmmirror.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe"
+  integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==
+  dependencies:
+    p-finally "^1.0.0"
+
 p-try@^2.0.0:
   version "2.2.0"
   resolved "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz"
@@ -5497,7 +5622,7 @@ utils-merge@1.0.1:
   resolved "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz"
   integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
 
-uuid@8.3.2, uuid@^8:
+uuid@8.3.2, uuid@^8, uuid@^8.3.0:
   version "8.3.2"
   resolved "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz"
   integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==