瀏覽代碼

feat(auth): refactor user login handling and integrate user creation in MongoDB

Dave 2 月之前
父節點
當前提交
f58cbf1cb0

+ 1 - 1
.env.mgnt

@@ -24,7 +24,7 @@ REDIS_PASSWORD=
 REDIS_DB=0
 REDIS_KEY_PREFIX=box:
 
-# RabbitMQ Config
+# RabbitMQ Config: RABBITMQ_URL="amqp://boxrabbit:BoxRabbit#2025@localhost:5672"
 RABBITMQ_URL="amqp://boxrabbit:BoxRabbit%232025@localhost:5672"
 RABBITMQ_LOGIN_EXCHANGE="stats.user"
 RABBITMQ_LOGIN_QUEUE="stats.user.login.q"

+ 2 - 2
apps/box-app-api/src/feature/auth/auth.controller.ts

@@ -5,7 +5,7 @@ import { Request } from 'express';
 import { LoginDto } from './login.dto';
 import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
 
-@ApiTags('认证')
+@ApiTags('授权')
 @Controller('auth')
 export class AuthController {
   constructor(private readonly authService: AuthService) {}
@@ -17,7 +17,7 @@ export class AuthController {
   })
   @ApiResponse({
     status: 200,
-    description: '登录成功,返回访问令牌',
+    description: '登录成功,返回访问token',
     schema: {
       type: 'object',
       properties: {

+ 10 - 1
apps/box-app-api/src/feature/auth/auth.service.ts

@@ -1,11 +1,12 @@
 // apps/box-app-api/src/auth/auth.service.ts
-import { Injectable } from '@nestjs/common';
+import { Injectable, Logger } from '@nestjs/common';
 import { JwtService } from '@nestjs/jwt';
 import { UserLoginEventPayload } from '@box/common/events/user-login-event.dto';
 import { RabbitmqPublisherService } from '../../rabbitmq/rabbitmq-publisher.service';
 
 @Injectable()
 export class AuthService {
+  private readonly logger = new Logger(AuthService.name);
   constructor(
     private readonly jwtService: JwtService,
     private readonly rabbitmqPublisher: RabbitmqPublisherService,
@@ -51,6 +52,7 @@ export class AuthService {
 
     // 4) Fire-and-forget publish (but wait for broker confirm)
     try {
+      this.logger.log(`Publishing user login event for uid=${uid}`);
       await this.rabbitmqPublisher.publishUserLogin(loginEvent);
     } catch (error) {
       // Decide your policy:
@@ -58,6 +60,13 @@ export class AuthService {
       //   - Or throw to fail login if stats are critical.
       // For now, let's log and continue.
       // If you want stricter behaviour, re-throw.
+      const errorMessage =
+        error instanceof Error ? error.message : String(error);
+      const errorStack = error instanceof Error ? error.stack : undefined;
+      this.logger.error(
+        `Failed to publish user login event for uid=${uid}: ${errorMessage}`,
+        errorStack,
+      );
     }
 
     return { accessToken };

+ 2 - 2
apps/box-stats-api/src/app.module.ts

@@ -1,7 +1,7 @@
 import { Module } from '@nestjs/common';
 import { ConfigModule } from '@nestjs/config';
 import { PrismaMongoModule } from './prisma/prisma-mongo.module';
-import { UserLoginHistoryModule } from './feature/user-login-history/user-login-history.module';
+import { UserLoginModule } from './feature/user-login/user-login.module';
 import { RabbitmqConsumerModule } from './feature/rabbitmq/rabbitmq-consumer.module';
 
 @Module({
@@ -11,7 +11,7 @@ import { RabbitmqConsumerModule } from './feature/rabbitmq/rabbitmq-consumer.mod
       envFilePath: ['.env.stats', '.env'], // adjust as needed
     }),
     PrismaMongoModule,
-    UserLoginHistoryModule,
+    UserLoginModule,
     RabbitmqConsumerModule,
   ],
 })

+ 2 - 2
apps/box-stats-api/src/feature/rabbitmq/rabbitmq-consumer.module.ts

@@ -1,9 +1,9 @@
 import { Module } from '@nestjs/common';
 import { RabbitmqConsumerService } from './rabbitmq-consumer.service';
-import { UserLoginHistoryModule } from '../user-login-history/user-login-history.module';
+import { UserLoginModule } from '../user-login/user-login.module';
 
 @Module({
-  imports: [UserLoginHistoryModule],
+  imports: [UserLoginModule],
   providers: [RabbitmqConsumerService],
 })
 export class RabbitmqConsumerModule {}

+ 2 - 2
apps/box-stats-api/src/feature/rabbitmq/rabbitmq-consumer.service.ts

@@ -7,7 +7,7 @@ import {
 import { ConfigService } from '@nestjs/config';
 import { Connection, Channel, ConsumeMessage } from 'amqplib';
 import * as amqp from 'amqplib';
-import { UserLoginHistoryService } from '../user-login-history/user-login-history.service';
+import { UserLoginService } from '../user-login/user-login.service';
 import { UserLoginEventPayload } from '@box/common/events/user-login-event.dto';
 
 @Injectable()
@@ -19,7 +19,7 @@ export class RabbitmqConsumerService implements OnModuleInit, OnModuleDestroy {
 
   constructor(
     private readonly config: ConfigService,
-    private readonly userLoginHistoryService: UserLoginHistoryService,
+    private readonly userLoginHistoryService: UserLoginService,
   ) {}
 
   async onModuleInit() {

+ 0 - 10
apps/box-stats-api/src/feature/user-login-history/user-login-history.module.ts

@@ -1,10 +0,0 @@
-import { Module } from '@nestjs/common';
-import { UserLoginHistoryService } from './user-login-history.service';
-import { PrismaMongoModule } from '../../prisma/prisma-mongo.module';
-
-@Module({
-  imports: [PrismaMongoModule],
-  providers: [UserLoginHistoryService],
-  exports: [UserLoginHistoryService],
-})
-export class UserLoginHistoryModule {}

+ 0 - 37
apps/box-stats-api/src/feature/user-login-history/user-login-history.service.ts

@@ -1,37 +0,0 @@
-import { Injectable, Logger } from '@nestjs/common';
-import { UserLoginEventPayload } from '@box/common/events/user-login-event.dto';
-import { PrismaMongoService } from '../../prisma/prisma-mongo.service';
-
-@Injectable()
-export class UserLoginHistoryService {
-  private readonly logger = new Logger(UserLoginHistoryService.name);
-
-  constructor(private readonly prisma: PrismaMongoService) {}
-
-  async recordLogin(event: UserLoginEventPayload): Promise<void> {
-    try {
-      const createAt =
-        typeof event.loginAt === 'bigint'
-          ? event.loginAt
-          : BigInt(event.loginAt);
-
-      await this.prisma.userLoginHistory.create({
-        data: {
-          uid: event.uid,
-          ip: event.ip,
-          userAgent: event.userAgent ?? null,
-          appVersion: event.appVersion ?? null,
-          os: event.os ?? null,
-          createAt,
-          tokenId: event.tokenId ?? null,
-        },
-      });
-    } catch (error: any) {
-      this.logger.error(
-        `Failed to record login for uid=${event.uid}: ${error.message}`,
-        error.stack,
-      );
-      throw error;
-    }
-  }
-}

+ 10 - 0
apps/box-stats-api/src/feature/user-login/user-login.module.ts

@@ -0,0 +1,10 @@
+import { Module } from '@nestjs/common';
+import { UserLoginService } from './user-login.service';
+import { PrismaMongoModule } from '../../prisma/prisma-mongo.module';
+
+@Module({
+  imports: [PrismaMongoModule],
+  providers: [UserLoginService],
+  exports: [UserLoginService],
+})
+export class UserLoginModule {}

+ 81 - 0
apps/box-stats-api/src/feature/user-login/user-login.service.ts

@@ -0,0 +1,81 @@
+import { Injectable, Logger } from '@nestjs/common';
+import { UserLoginEventPayload } from '@box/common/events/user-login-event.dto';
+import { PrismaMongoService } from '../../prisma/prisma-mongo.service';
+
+@Injectable()
+export class UserLoginService {
+  private readonly logger = new Logger(UserLoginService.name);
+
+  constructor(private readonly prisma: PrismaMongoService) {}
+
+  // create a function createUser, to create a new user in mongo-stats database model User with fields from event payload
+  async createUser(event: UserLoginEventPayload): Promise<void> {
+    try {
+      // upsert user record based on uid, if user exists, update lastLoginAt, else create new user
+      this.logger.log(`Creating or updating user for uid=${event.uid}`);
+      await this.prisma.user.upsert({
+        where: { uid: event.uid },
+        update: {
+          lastLoginAt:
+            typeof event.loginAt === 'bigint'
+              ? event.loginAt
+              : BigInt(event.loginAt),
+          ip: event.ip,
+          os: event.os ?? null,
+        },
+        create: {
+          uid: event.uid,
+          os: event.os ?? null,
+          createAt:
+            typeof event.loginAt === 'bigint'
+              ? event.loginAt
+              : BigInt(event.loginAt),
+          lastLoginAt:
+            typeof event.loginAt === 'bigint'
+              ? event.loginAt
+              : BigInt(event.loginAt),
+          ip: event.ip,
+        },
+      });
+    } catch (error: any) {
+      this.logger.error(
+        `Failed to create user for uid=${event.uid}: ${error.message}`,
+        error.stack,
+      );
+      throw error;
+    }
+  }
+
+  async recordLogin(event: UserLoginEventPayload): Promise<void> {
+    try {
+      this.logger.log(
+        `Recording login for uid=${event.uid} from ip=${event.ip}`,
+      );
+      const createAt =
+        typeof event.loginAt === 'bigint'
+          ? event.loginAt
+          : BigInt(event.loginAt);
+
+      await this.prisma.userLoginHistory.create({
+        data: {
+          uid: event.uid,
+          ip: event.ip,
+          userAgent: event.userAgent ?? null,
+          appVersion: event.appVersion ?? null,
+          os: event.os ?? null,
+          createAt,
+          tokenId: event.tokenId ?? null,
+        },
+      });
+
+      // Also update or create user record
+      await this.createUser(event);
+    } catch (error: any) {
+      this.logger.error(
+        `Failed to record login for uid=${event.uid}: ${error.message}`,
+        error.stack,
+      );
+      throw error;
+    }
+  }
+}

+ 1 - 0
prisma/mongo-stats/schema/user.prisma

@@ -2,6 +2,7 @@ model User {
   id            String     @id @map("_id") @default(auto()) @db.ObjectId
   uid           String     @unique          // 唯一设备码
   ip            String                      // 最近登录 IP
+  os          String?                         // iOS / Android / Browser
 
   createAt      BigInt     @default(0)      // 注册/创建时间
   lastLoginAt   BigInt     @default(0)      // 最后登录时间