Selaa lähdekoodia

完成非法房主

gemercheung 2 vuotta sitten
vanhempi
commit
ad9fd51f97

+ 2 - 1
.env.development

@@ -1,5 +1,6 @@
+APP_PORT=8888
 SOCKET_NAME='socket-test'
-SOCKET_PORT=6666
+SOCKET_PORT=8889
 SOCKET_PATH="/ws-sync"
 REDIS_HOST=127.0.0.1
 REDIS_PORT=6379

+ 2 - 1
.env.production

@@ -1,5 +1,6 @@
+APP_PORT=8888
 SOCKET_NAME='socket-test'
-SOCKET_PORT=6666
+SOCKET_PORT=8889
 SOCKET_PATH="/ws-sync"
 REDIS_HOST=127.0.0.1
 REDIS_PORT=6379

+ 5 - 0
log/.46f0af219249542fdc264b9b0460d72fee271bbf-audit.json

@@ -9,6 +9,11 @@
             "date": 1676369333465,
             "name": "D:\\codes\\daikan\\socket-server-v4\\log\\2023-02-14.log",
             "hash": "2ab3113bffdc301303be9f0789c1993a5dd376f54ab83789f1cafe128dbac5a8"
+        },
+        {
+            "date": 1676422429133,
+            "name": "D:\\codes\\daikan\\socket-server-v4\\log\\2023-02-15.log",
+            "hash": "b1410283ed4fb79aeeb9d673071592ae53cf16293856d13796ee9dd324fe8c08"
         }
     ],
     "hashType": "sha256"

+ 1 - 0
package.json

@@ -33,6 +33,7 @@
     "@socket.io/redis-adapter": "^8.1.0",
     "bcrypt": "^5.1.0",
     "chalk": "^4",
+    "dotenv": "^16.0.3",
     "ioredis": "^5.3.1",
     "logform": "^2.5.1",
     "nest-winston": "^1.8.0",

+ 2 - 0
pnpm-lock.yaml

@@ -23,6 +23,7 @@ specifiers:
   '@typescript-eslint/parser': ^5.0.0
   bcrypt: ^5.1.0
   chalk: ^4
+  dotenv: ^16.0.3
   eslint: ^8.0.1
   eslint-config-prettier: ^8.3.0
   eslint-plugin-prettier: ^4.0.0
@@ -61,6 +62,7 @@ dependencies:
   '@socket.io/redis-adapter': 8.1.0_socket.io-adapter@2.5.2
   bcrypt: 5.1.0
   chalk: 4.1.2
+  dotenv: 16.0.3
   ioredis: 5.3.1
   logform: 2.5.1
   nest-winston: 1.8.0_irohmcczkgvfvkzx5ptmxekhyy

+ 11 - 8
src/app.module.ts

@@ -1,15 +1,17 @@
+import { ConfigModule } from '@nestjs/config';
+const config = ConfigModule.forRoot({
+  isGlobal: true,
+  envFilePath: ['.env.development', '.env.production'],
+});
+
 import { Module } from '@nestjs/common';
 import { AppController } from './app.controller';
 import { AppService } from './app.service';
-import { ChatGateway } from './chat/chat.gateway';
 import { RedisModule, RedisService } from '@liaoliaots/nestjs-redis';
 import { ThrottlerModule } from '@nestjs/throttler';
 import { ThrottlerStorageRedisService } from 'nestjs-throttler-storage-redis';
-import { ConfigModule } from '@nestjs/config';
-
-const config = ConfigModule.forRoot({
-  envFilePath: ['.env.development', '.env.production'],
-});
+import { RoomModule } from './room/room.module';
+// import { SocketModule } from './socket/socket.module';
 
 const url = `redis://:${process.env.REDIS_PASSWORD}@${process.env.REDIS_HOST}:${process.env.REDIS_PORT}/${process.env.REDIS_DB}`;
 
@@ -30,8 +32,9 @@ const storeThor = ThrottlerModule.forRootAsync({
   inject: [RedisService],
 });
 @Module({
-  imports: [config, redisMo, storeThor],
+  imports: [config, redisMo, storeThor, RoomModule],
   controllers: [AppController],
-  providers: [AppService, ChatGateway],
+  providers: [AppService],
+  exports: [],
 })
 export class AppModule {}

+ 0 - 18
src/chat/chat.gateway.spec.ts

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

+ 3 - 3
src/main.ts

@@ -41,12 +41,12 @@ async function bootstrap() {
   app.useWebSocketAdapter(redisIoAdapter);
 
   const config = new DocumentBuilder()
-    .setTitle('带看api文档')
-    .setDescription('The cats API description')
+    .setTitle('带看socket API')
+    .setDescription('带看api文档')
     .setVersion('1.0')
     .build();
   const document = SwaggerModule.createDocument(app, config);
   SwaggerModule.setup('api', app, document);
-  await app.listen(8888);
+  await app.listen(process.env.APP_PORT);
 }
 bootstrap();

+ 16 - 0
src/room/config.d.ts

@@ -0,0 +1,16 @@
+interface RoomConfigType {
+  limit: IntRange<5, 50>;
+  masterId: string;
+}
+
+type Enumerate<
+  N extends number,
+  Acc extends number[] = [],
+> = Acc['length'] extends N
+  ? Acc[number]
+  : Enumerate<N, [...Acc, Acc['length']]>;
+
+type IntRange<F extends number, T extends number> = Exclude<
+  Enumerate<T>,
+  Enumerate<F>
+>;

+ 12 - 0
src/room/room.module.ts

@@ -0,0 +1,12 @@
+import { Module } from '@nestjs/common';
+import { SocketGateway } from 'src/socket/socket.gateway';
+
+import { RoomService } from './room.service';
+import { UsersService } from './users/users.service';
+
+@Module({
+  imports: [],
+  providers: [RoomService, SocketGateway, UsersService],
+  exports: [RoomService],
+})
+export class RoomModule {}

+ 131 - 0
src/room/room.service.ts

@@ -0,0 +1,131 @@
+import { InjectRedis } from '@liaoliaots/nestjs-redis';
+import { forwardRef, Inject, Injectable } from '@nestjs/common';
+import { Redis } from 'ioredis';
+
+import { SocketGateway } from 'src/socket/socket.gateway';
+import { UsersService } from './users/users.service';
+
+@Injectable()
+export class RoomService {
+  constructor(
+    @Inject(forwardRef(() => SocketGateway))
+    private readonly socketGateway: SocketGateway,
+    @InjectRedis() private readonly redis: Redis,
+    private readonly userService: UsersService,
+  ) {}
+
+  private _userInfo = {} as UserInfoType;
+  private _roomConfig = {} as RoomConfigType;
+
+  private get _roomId(): string {
+    return this._userInfo.RoomId;
+  }
+  private get _userId(): string {
+    return this._userInfo.UserId;
+  }
+  private get _isLeader(): boolean {
+    return this._userInfo.Role === 'leader';
+  }
+
+  /**
+   * 初始化当前用户数据
+   * @param userInfo
+   */
+  initUserProfile(userInfo: UserInfoParams): void {
+    console.log('userInfo', userInfo);
+    this._userInfo = {
+      RoomId: userInfo.roomId,
+      Role: userInfo.role,
+      UserId: userInfo.userId,
+      Avatar: userInfo.avatar,
+      Nickname: userInfo.nickname,
+      IsClient: userInfo.isClient,
+      IsMuted: true,
+      IsWords: true,
+      Order: userInfo.role === 'leader' ? 0 : 1,
+      JoinTime: Date.now(),
+      InTime: Date.now(),
+    };
+  }
+  /**
+   * 初始化房间配置
+   * @param userInfo
+   */
+  async handleRoomConfig(
+    RoomId: string,
+    RoomConfig: RoomConfigType,
+  ): Promise<void> {
+    await this.userService.setRoomConfig(RoomId, RoomConfig);
+  }
+  /**
+   * 加入房间
+   * @param userInfo
+   */
+
+  async handleUserJoin(userInfo: UserInfoParams): Promise<void> {
+    this.initUserProfile(userInfo);
+    console.log(this._userId);
+    let blockJoin = false;
+    if (this._roomId?.length && this._userId?.length) {
+      //房主设置房间配置
+      if (this._isLeader) {
+        const isValid = await this.userService.isRoomMaster(
+          this._roomId,
+          this._userId,
+        );
+
+        if (isValid) {
+          await this.userService.setRoomConfig(
+            this._roomId,
+            userInfo.roomConfig,
+          );
+        } else {
+          blockJoin = true;
+          this.socketGateway.server.emit('action', {
+            type: 'invalid-master',
+            code: 303,
+          });
+        }
+      }
+      const isExist = await this.userService.isUserInRooms(
+        this._roomId,
+        this._userId,
+      );
+      if (!isExist) {
+        if (this._isLeader) {
+          this._roomConfig = userInfo.roomConfig;
+          this.handleRoomConfig(this._roomId, this._roomConfig);
+        }
+        this.userService.insertUser(this._userInfo);
+      } else {
+        this.userService.updateUsers(this._userInfo);
+      }
+      const roomUsers = await this.userService.getRoomUsers(this._roomId);
+
+      if (!blockJoin) {
+        this.socketGateway.server.emit('join', {
+          user: this._userInfo,
+          members: roomUsers,
+        });
+        if (!this._userInfo.IsClient) {
+          this.socketGateway.server.to(this._roomId).emit('action', {
+            type: 'user-join',
+            user: this._userInfo,
+            members: roomUsers,
+          });
+        }
+      }
+    } else {
+      this.socketGateway.server.emit('action', { type: 'error', code: 403 });
+    }
+  }
+
+  /**
+   * 解散房间
+   */
+
+  async handleRoomDismiss(): Promise<void> {
+    await this.userService.delRoom(this._roomId);
+    await this.userService.delRoomConfig(this._roomId);
+  }
+}

+ 23 - 0
src/room/user.d.ts

@@ -0,0 +1,23 @@
+interface UserInfoType {
+  UserId: string;
+  RoomId: string;
+  Role: string;
+  Avatar: string;
+  Nickname: string;
+  IsClient?: boolean;
+  IsMuted?: boolean;
+  IsWords?: boolean;
+  JoinTime?: Timestamp;
+  InTime?: Timestamp;
+  Order?: number;
+}
+
+interface UserInfoParams {
+  userId: string;
+  roomId: string;
+  role: string;
+  avatar: string;
+  nickname: string;
+  isClient: boolean;
+  roomConfig?: RoomConfigType;
+}

+ 81 - 0
src/room/users/users.service.ts

@@ -0,0 +1,81 @@
+import { InjectRedis } from '@liaoliaots/nestjs-redis';
+import { Injectable } from '@nestjs/common';
+import { Redis } from 'ioredis';
+
+@Injectable()
+export class UsersService {
+  constructor(@InjectRedis() private readonly redis: Redis) {}
+
+  async isUserInRooms(RoomId: string, UserId: string): Promise<boolean> {
+    const res = await this.redis.hexists(
+      `kankan:socket:rooms:${RoomId}`,
+      UserId,
+    );
+
+    return res > 0 ? true : false;
+  }
+  insertUser(useInfo: UserInfoType): Promise<any> {
+    return this.redis.hset(
+      `kankan:socket:rooms:${useInfo.RoomId}`,
+      useInfo.UserId,
+      JSON.stringify(useInfo),
+    );
+  }
+  async updateUsers(useInfo: UserInfoType): Promise<void> {
+    const user = await this.redis.hget(
+      `kankan:socket:rooms:${useInfo.RoomId}`,
+      useInfo.UserId,
+    );
+    const userObj = JSON.parse(user) as any as UserInfoType;
+    const updateInfo: UserInfoType = Object.assign({}, userObj, {
+      JoinTime: useInfo.JoinTime,
+      InTime: useInfo.InTime,
+      Avatar: useInfo.Avatar,
+      Nickname: useInfo.Nickname,
+    });
+
+    await this.redis.hset(
+      `kankan:socket:rooms:${useInfo.RoomId}`,
+      useInfo.UserId,
+      JSON.stringify(updateInfo),
+    );
+  }
+
+  async getRoomUsers(RoomId: string): Promise<UserInfoType[]> {
+    const res = await this.redis.hvals(`kankan:socket:rooms:${RoomId}`);
+    const allUsers = Array.from(res)
+      .map((i) => JSON.parse(i))
+      .sort((a, b) => parseInt(a.Order) - parseInt(b.Order));
+    return allUsers as any as UserInfoType[];
+  }
+  delRoom(RoomId: string): Promise<any> {
+    return this.redis.del(`kankan:socket:rooms:${RoomId}`);
+  }
+  delRoomConfig(RoomId: string): Promise<any> {
+    return this.redis.hdel(`kankan:socket:roomConfig`, RoomId);
+  }
+  async setRoomConfig(
+    RoomId: string,
+    RoomConfig: RoomConfigType,
+  ): Promise<void> {
+    this.redis.hset(
+      `kankan:socket:roomConfig`,
+      RoomId,
+      JSON.stringify(RoomConfig),
+    );
+  }
+  async isRoomMaster(RoomId: string, userId: string): Promise<boolean> {
+    try {
+      const exist = await this.redis.hget(`kankan:socket:roomConfig`, RoomId);
+      if (exist) {
+        const master: RoomConfigType = JSON.parse(exist);
+        console.log('isRoomMaster', master.masterId);
+        return master.masterId === userId;
+      } else {
+        return false;
+      }
+    } catch (error) {
+      return false;
+    }
+  }
+}

+ 15 - 15
src/chat/chat.gateway.ts

@@ -13,17 +13,25 @@ import { Server, Socket } from 'socket.io';
 import Redis from 'ioredis';
 import { instrument } from '@socket.io/admin-ui';
 import * as bcrypt from 'bcrypt';
+import { RoomService } from '../room/room.service';
 
-@WebSocketGateway(8889, {
+console.log('SOCKET_PATH-0', process.env.SOCKET_PATH);
+@WebSocketGateway(Number(process.env.SOCKET_PORT), {
   transports: ['websocket'],
   cors: '*',
   // namespace: "ws",
+  // path: '/ws-sync',
   path: process.env.SOCKET_PATH,
   // parser: require('socket.io-msgpack-parser'),
 })
-export class ChatGateway
-  implements OnGatewayInit, OnGatewayDisconnect, OnGatewayConnection {
-  constructor(@InjectRedis() private readonly redis: Redis) { }
+export class SocketGateway
+  implements OnGatewayInit, OnGatewayDisconnect, OnGatewayConnection
+{
+  constructor(
+    @InjectRedis() private readonly redis: Redis,
+    private readonly roomService: RoomService,
+  ) {}
+
   @WebSocketServer() server: Server;
   handleConnection(client: any, ...args: any[]) {
     // console.log('handleConnection', client, args);
@@ -32,6 +40,7 @@ export class ChatGateway
     // console.log('handleDisconnect', client);
   }
   afterInit(server: any) {
+    console.log('SOCKET_PATH-1', process.env.SOCKET_PATH);
     instrument(server, {
       auth: {
         type: 'basic',
@@ -44,19 +53,10 @@ export class ChatGateway
 
   @SubscribeMessage('join')
   async handleMessage(
-    @MessageBody() message: any,
+    @MessageBody() message: UserInfoParams,
     @ConnectedSocket() socket: Socket,
   ): Promise<void> {
-    console.log('join', message);
     socket.join(message.roomId);
-    const tt = await this.redis.get('test');
-    console.log('redis', tt);
-    this.server.to(message.roomId).emit('action', { type: 'user-join' });
-
-    // socket.emit('join', { test: true });
-    // socket.broadcast
-    //   .to(message.roomId)
-    //   .emit('action',
-    //    { type: 'user-join',{ test: true } });
+    await this.roomService.handleUserJoin(message);
   }
 }

+ 11 - 0
src/socket/socket.module.ts

@@ -0,0 +1,11 @@
+import { forwardRef, Module } from '@nestjs/common';
+import { RoomModule } from 'src/room/room.module';
+// import { RoomService } from '../room/room.service';
+import { SocketGateway } from './socket.gateway';
+
+@Module({
+  imports: [forwardRef(() => RoomModule)],
+  providers: [SocketGateway],
+  exports: [SocketGateway],
+})
+export class SocketModule {}