diff --git a/backend/services/presence-service/nest-cli.json b/backend/services/presence-service/nest-cli.json new file mode 100644 index 00000000..f9aa683b --- /dev/null +++ b/backend/services/presence-service/nest-cli.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true + } +} diff --git a/backend/services/presence-service/package.json b/backend/services/presence-service/package.json new file mode 100644 index 00000000..9c0a32af --- /dev/null +++ b/backend/services/presence-service/package.json @@ -0,0 +1,57 @@ +{ + "name": "presence-service", + "version": "1.0.0", + "description": "RWA Presence Service - User Presence & DAU Tracking", + "author": "RWA Team", + "private": true, + "license": "UNLICENSED", + "prisma": { + "schema": "prisma/schema.prisma" + }, + "scripts": { + "build": "nest build", + "format": "prettier --write \"src/**/*.ts\"", + "start": "nest start", + "start:dev": "nest start --watch", + "start:debug": "nest start --debug --watch", + "start:prod": "node dist/main", + "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", + "prisma:generate": "prisma generate", + "prisma:migrate": "prisma migrate dev", + "prisma:migrate:prod": "prisma migrate deploy", + "prisma:studio": "prisma studio" + }, + "dependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/config": "^3.1.1", + "@nestjs/core": "^10.0.0", + "@nestjs/platform-express": "^10.0.0", + "@nestjs/schedule": "^4.0.0", + "@nestjs/swagger": "^7.1.17", + "@prisma/client": "^5.7.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.0", + "ioredis": "^5.3.2", + "reflect-metadata": "^0.1.13", + "rxjs": "^7.8.1" + }, + "devDependencies": { + "@nestjs/cli": "^10.0.0", + "@nestjs/schematics": "^10.0.0", + "@nestjs/testing": "^10.0.0", + "@types/express": "^4.17.17", + "@types/node": "^20.3.1", + "@typescript-eslint/eslint-plugin": "^6.0.0", + "@typescript-eslint/parser": "^6.0.0", + "eslint": "^8.42.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-prettier": "^5.0.0", + "prettier": "^3.0.0", + "prisma": "^5.7.0", + "source-map-support": "^0.5.21", + "ts-loader": "^9.4.3", + "ts-node": "^10.9.1", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.1.3" + } +} diff --git a/backend/services/presence-service/prisma/schema.prisma b/backend/services/presence-service/prisma/schema.prisma new file mode 100644 index 00000000..14640ee0 --- /dev/null +++ b/backend/services/presence-service/prisma/schema.prisma @@ -0,0 +1,92 @@ +// ============================================================================= +// Presence Service - Prisma Schema +// ============================================================================= + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +// ============================================================================= +// Heartbeat (在线心跳记录) +// ============================================================================= + +model Heartbeat { + id String @id @default(uuid()) + installId String // 设备唯一安装ID + sessionId String // 会话ID + userId String? // 用户ID (可选,未登录时为null) + timestamp DateTime // 心跳时间戳 + createdAt DateTime @default(now()) + + @@index([installId, timestamp]) + @@index([userId, timestamp]) + @@index([sessionId]) + @@index([timestamp]) + @@map("heartbeats") +} + +// ============================================================================= +// Daily Active Users (DAU 日活统计) +// ============================================================================= + +model DailyActiveUser { + id String @id @default(uuid()) + date DateTime @db.Date // 统计日期 (YYYY-MM-DD) + installId String // 设备唯一安装ID + userId String? // 用户ID (可选) + firstSeen DateTime // 当日首次出现时间 + lastSeen DateTime // 当日最后出现时间 + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@unique([date, installId]) + @@index([date]) + @@index([userId, date]) + @@map("daily_active_users") +} + +// ============================================================================= +// Online Presence (实时在线状态) +// 使用 Redis 存储,此表仅用于历史记录 +// ============================================================================= + +model OnlineSession { + id String @id @default(uuid()) + installId String // 设备唯一安装ID + sessionId String @unique // 会话ID + userId String? // 用户ID (可选) + startTime DateTime // 会话开始时间 + lastHeartbeat DateTime // 最后心跳时间 + endTime DateTime? // 会话结束时间 (null表示仍在线) + duration Int? // 会话时长 (秒) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([installId]) + @@index([userId]) + @@index([sessionId]) + @@index([startTime]) + @@map("online_sessions") +} + +// ============================================================================= +// DAU Statistics (DAU 统计汇总) +// ============================================================================= + +model DauStatistics { + id String @id @default(uuid()) + date DateTime @unique @db.Date // 统计日期 + totalDau Int // 总DAU (按installId去重) + uniqueUsers Int // 唯一用户数 (按userId去重, 不含匿名) + anonymousUsers Int // 匿名用户数 (userId为null) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([date]) + @@map("dau_statistics") +} diff --git a/backend/services/presence-service/tsconfig.json b/backend/services/presence-service/tsconfig.json new file mode 100644 index 00000000..bd3c3946 --- /dev/null +++ b/backend/services/presence-service/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2021", + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./", + "incremental": true, + "skipLibCheck": true, + "strictNullChecks": true, + "noImplicitAny": true, + "strictBindCallApply": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "paths": { + "@/*": ["src/*"] + } + } +}