From 1469b457239beb59c05dceda6c1ca3a50f4ca23f Mon Sep 17 00:00:00 2001 From: hailin Date: Mon, 24 Nov 2025 07:49:40 +0000 Subject: [PATCH] . --- backend/services/identity-service/Dockerfile | 4 +- .../identity-service/.env.development | 29 -- .../identity-service/.env.example | 29 -- .../identity-service/.env.production | 29 -- .../identity-service/Dockerfile | 30 -- .../identity-service/README.md | 124 ----- .../identity-service/database/init.sql | 7 - .../identity-service/docker-compose.yml | 69 --- .../identity-service/nest-cli.json | 17 - .../identity-service/package.json | 87 ---- .../identity-service/src/api/api.module.ts | 10 - .../src/api/controllers/auth.controller.ts | 19 - .../controllers/user-account.controller.ts | 169 ------- .../identity-service/src/api/dto/index.ts | 182 -------- .../dto/request/auto-create-account.dto.ts | 30 -- .../src/api/dto/request/bind-phone.dto.ts | 14 - .../src/api/dto/request/index.ts | 5 - .../dto/request/recover-by-mnemonic.dto.ts | 23 - .../api/dto/request/recover-by-phone.dto.ts | 28 -- .../src/api/dto/request/submit-kyc.dto.ts | 24 - .../src/api/dto/response/device.dto.ts | 18 - .../src/api/dto/response/index.ts | 2 - .../src/api/dto/response/user-profile.dto.ts | 64 --- .../src/api/validators/phone.validator.ts | 47 -- .../identity-service/src/app.module.ts | 100 ----- .../src/application/application.module.ts | 36 -- .../auto-create-account.command.ts | 9 - .../auto-create-account.handler.ts | 80 ---- .../commands/bind-phone/bind-phone.command.ts | 7 - .../commands/bind-phone/bind-phone.handler.ts | 37 -- .../src/application/commands/index.ts | 206 --------- .../recover-by-mnemonic.command.ts | 8 - .../recover-by-mnemonic.handler.ts | 62 --- .../recover-by-phone.command.ts | 9 - .../recover-by-phone.handler.ts | 58 --- .../get-my-devices/get-my-devices.handler.ts | 27 -- .../get-my-devices/get-my-devices.query.ts | 6 - .../get-my-profile/get-my-profile.handler.ts | 46 -- .../get-my-profile/get-my-profile.query.ts | 3 - .../src/application/services/token.service.ts | 93 ---- .../services/user-application.service.ts | 424 ------------------ .../identity-service/src/config/app.config.ts | 4 - .../src/config/database.config.ts | 3 - .../identity-service/src/config/index.ts | 36 -- .../identity-service/src/config/jwt.config.ts | 5 - .../src/config/kafka.config.ts | 5 - .../src/config/redis.config.ts | 6 - .../user-account/user-account.aggregate.ts | 347 -------------- .../user-account/user-account.factory.ts | 29 -- .../user-account/user-account.spec.ts | 79 ---- .../src/domain/domain.module.ts | 25 -- .../domain/entities/wallet-address.entity.ts | 167 ------- .../src/domain/events/device-added.event.ts | 18 - .../src/domain/events/index.ts | 174 ------- .../src/domain/events/phone-bound.event.ts | 11 - .../events/user-account-created.event.ts | 22 - .../user-account.repository.interface.ts | 30 -- .../account-sequence-generator.service.ts | 15 - .../src/domain/services/index.ts | 121 ----- .../domain/services/user-validator.service.ts | 51 --- .../services/wallet-generator.service.ts | 53 --- .../value-objects/account-sequence.vo.ts | 19 - .../domain/value-objects/device-info.vo.ts | 20 - .../src/domain/value-objects/index.ts | 262 ----------- .../src/domain/value-objects/kyc-info.vo.ts | 25 -- .../src/domain/value-objects/mnemonic.vo.ts | 32 -- .../domain/value-objects/phone-number.vo.ts | 21 - .../domain/value-objects/referral-code.vo.ts | 26 -- .../external/blockchain/blockchain.module.ts | 8 - .../wallet-generator.service.impl.ts | 53 --- .../infrastructure/external/sms/sms.module.ts | 8 - .../external/sms/sms.service.ts | 23 - .../infrastructure/infrastructure.module.ts | 31 -- .../kafka/event-publisher.service.ts | 53 --- .../src/infrastructure/kafka/kafka.module.ts | 8 - .../entities/user-account.entity.ts | 44 -- .../entities/user-device.entity.ts | 8 - .../entities/wallet-address.entity.ts | 9 - .../mappers/user-account.mapper.ts | 57 --- .../persistence/prisma/migrations/.gitkeep | 1 - .../persistence/prisma/prisma.service.ts | 13 - .../persistence/prisma/schema.prisma | 133 ------ .../user-account.repository.impl.ts | 233 ---------- .../src/infrastructure/redis/redis.module.ts | 8 - .../src/infrastructure/redis/redis.service.ts | 50 --- .../identity-service/src/main.ts | 45 -- .../decorators/current-user.decorator.ts | 10 - .../src/shared/decorators/public.decorator.ts | 4 - .../exceptions/application.exception.ts | 11 - .../src/shared/exceptions/domain.exception.ts | 40 -- .../shared/filters/domain-exception.filter.ts | 17 - .../shared/filters/global-exception.filter.ts | 65 --- .../src/shared/guards/jwt-auth.guard.ts | 68 --- .../interceptors/transform.interceptor.ts | 21 - .../identity-service/tsconfig.json | 24 - .../services/identity-service/package.json | 3 + 96 files changed, 5 insertions(+), 4920 deletions(-) delete mode 100644 backend/services/identity-service/identity-service/.env.development delete mode 100644 backend/services/identity-service/identity-service/.env.example delete mode 100644 backend/services/identity-service/identity-service/.env.production delete mode 100644 backend/services/identity-service/identity-service/Dockerfile delete mode 100644 backend/services/identity-service/identity-service/README.md delete mode 100644 backend/services/identity-service/identity-service/database/init.sql delete mode 100644 backend/services/identity-service/identity-service/docker-compose.yml delete mode 100644 backend/services/identity-service/identity-service/nest-cli.json delete mode 100644 backend/services/identity-service/identity-service/package.json delete mode 100644 backend/services/identity-service/identity-service/src/api/api.module.ts delete mode 100644 backend/services/identity-service/identity-service/src/api/controllers/auth.controller.ts delete mode 100644 backend/services/identity-service/identity-service/src/api/controllers/user-account.controller.ts delete mode 100644 backend/services/identity-service/identity-service/src/api/dto/index.ts delete mode 100644 backend/services/identity-service/identity-service/src/api/dto/request/auto-create-account.dto.ts delete mode 100644 backend/services/identity-service/identity-service/src/api/dto/request/bind-phone.dto.ts delete mode 100644 backend/services/identity-service/identity-service/src/api/dto/request/index.ts delete mode 100644 backend/services/identity-service/identity-service/src/api/dto/request/recover-by-mnemonic.dto.ts delete mode 100644 backend/services/identity-service/identity-service/src/api/dto/request/recover-by-phone.dto.ts delete mode 100644 backend/services/identity-service/identity-service/src/api/dto/request/submit-kyc.dto.ts delete mode 100644 backend/services/identity-service/identity-service/src/api/dto/response/device.dto.ts delete mode 100644 backend/services/identity-service/identity-service/src/api/dto/response/index.ts delete mode 100644 backend/services/identity-service/identity-service/src/api/dto/response/user-profile.dto.ts delete mode 100644 backend/services/identity-service/identity-service/src/api/validators/phone.validator.ts delete mode 100644 backend/services/identity-service/identity-service/src/app.module.ts delete mode 100644 backend/services/identity-service/identity-service/src/application/application.module.ts delete mode 100644 backend/services/identity-service/identity-service/src/application/commands/auto-create-account/auto-create-account.command.ts delete mode 100644 backend/services/identity-service/identity-service/src/application/commands/auto-create-account/auto-create-account.handler.ts delete mode 100644 backend/services/identity-service/identity-service/src/application/commands/bind-phone/bind-phone.command.ts delete mode 100644 backend/services/identity-service/identity-service/src/application/commands/bind-phone/bind-phone.handler.ts delete mode 100644 backend/services/identity-service/identity-service/src/application/commands/index.ts delete mode 100644 backend/services/identity-service/identity-service/src/application/commands/recover-by-mnemonic/recover-by-mnemonic.command.ts delete mode 100644 backend/services/identity-service/identity-service/src/application/commands/recover-by-mnemonic/recover-by-mnemonic.handler.ts delete mode 100644 backend/services/identity-service/identity-service/src/application/commands/recover-by-phone/recover-by-phone.command.ts delete mode 100644 backend/services/identity-service/identity-service/src/application/commands/recover-by-phone/recover-by-phone.handler.ts delete mode 100644 backend/services/identity-service/identity-service/src/application/queries/get-my-devices/get-my-devices.handler.ts delete mode 100644 backend/services/identity-service/identity-service/src/application/queries/get-my-devices/get-my-devices.query.ts delete mode 100644 backend/services/identity-service/identity-service/src/application/queries/get-my-profile/get-my-profile.handler.ts delete mode 100644 backend/services/identity-service/identity-service/src/application/queries/get-my-profile/get-my-profile.query.ts delete mode 100644 backend/services/identity-service/identity-service/src/application/services/token.service.ts delete mode 100644 backend/services/identity-service/identity-service/src/application/services/user-application.service.ts delete mode 100644 backend/services/identity-service/identity-service/src/config/app.config.ts delete mode 100644 backend/services/identity-service/identity-service/src/config/database.config.ts delete mode 100644 backend/services/identity-service/identity-service/src/config/index.ts delete mode 100644 backend/services/identity-service/identity-service/src/config/jwt.config.ts delete mode 100644 backend/services/identity-service/identity-service/src/config/kafka.config.ts delete mode 100644 backend/services/identity-service/identity-service/src/config/redis.config.ts delete mode 100644 backend/services/identity-service/identity-service/src/domain/aggregates/user-account/user-account.aggregate.ts delete mode 100644 backend/services/identity-service/identity-service/src/domain/aggregates/user-account/user-account.factory.ts delete mode 100644 backend/services/identity-service/identity-service/src/domain/aggregates/user-account/user-account.spec.ts delete mode 100644 backend/services/identity-service/identity-service/src/domain/domain.module.ts delete mode 100644 backend/services/identity-service/identity-service/src/domain/entities/wallet-address.entity.ts delete mode 100644 backend/services/identity-service/identity-service/src/domain/events/device-added.event.ts delete mode 100644 backend/services/identity-service/identity-service/src/domain/events/index.ts delete mode 100644 backend/services/identity-service/identity-service/src/domain/events/phone-bound.event.ts delete mode 100644 backend/services/identity-service/identity-service/src/domain/events/user-account-created.event.ts delete mode 100644 backend/services/identity-service/identity-service/src/domain/repositories/user-account.repository.interface.ts delete mode 100644 backend/services/identity-service/identity-service/src/domain/services/account-sequence-generator.service.ts delete mode 100644 backend/services/identity-service/identity-service/src/domain/services/index.ts delete mode 100644 backend/services/identity-service/identity-service/src/domain/services/user-validator.service.ts delete mode 100644 backend/services/identity-service/identity-service/src/domain/services/wallet-generator.service.ts delete mode 100644 backend/services/identity-service/identity-service/src/domain/value-objects/account-sequence.vo.ts delete mode 100644 backend/services/identity-service/identity-service/src/domain/value-objects/device-info.vo.ts delete mode 100644 backend/services/identity-service/identity-service/src/domain/value-objects/index.ts delete mode 100644 backend/services/identity-service/identity-service/src/domain/value-objects/kyc-info.vo.ts delete mode 100644 backend/services/identity-service/identity-service/src/domain/value-objects/mnemonic.vo.ts delete mode 100644 backend/services/identity-service/identity-service/src/domain/value-objects/phone-number.vo.ts delete mode 100644 backend/services/identity-service/identity-service/src/domain/value-objects/referral-code.vo.ts delete mode 100644 backend/services/identity-service/identity-service/src/infrastructure/external/blockchain/blockchain.module.ts delete mode 100644 backend/services/identity-service/identity-service/src/infrastructure/external/blockchain/wallet-generator.service.impl.ts delete mode 100644 backend/services/identity-service/identity-service/src/infrastructure/external/sms/sms.module.ts delete mode 100644 backend/services/identity-service/identity-service/src/infrastructure/external/sms/sms.service.ts delete mode 100644 backend/services/identity-service/identity-service/src/infrastructure/infrastructure.module.ts delete mode 100644 backend/services/identity-service/identity-service/src/infrastructure/kafka/event-publisher.service.ts delete mode 100644 backend/services/identity-service/identity-service/src/infrastructure/kafka/kafka.module.ts delete mode 100644 backend/services/identity-service/identity-service/src/infrastructure/persistence/entities/user-account.entity.ts delete mode 100644 backend/services/identity-service/identity-service/src/infrastructure/persistence/entities/user-device.entity.ts delete mode 100644 backend/services/identity-service/identity-service/src/infrastructure/persistence/entities/wallet-address.entity.ts delete mode 100644 backend/services/identity-service/identity-service/src/infrastructure/persistence/mappers/user-account.mapper.ts delete mode 100644 backend/services/identity-service/identity-service/src/infrastructure/persistence/prisma/migrations/.gitkeep delete mode 100644 backend/services/identity-service/identity-service/src/infrastructure/persistence/prisma/prisma.service.ts delete mode 100644 backend/services/identity-service/identity-service/src/infrastructure/persistence/prisma/schema.prisma delete mode 100644 backend/services/identity-service/identity-service/src/infrastructure/persistence/repositories/user-account.repository.impl.ts delete mode 100644 backend/services/identity-service/identity-service/src/infrastructure/redis/redis.module.ts delete mode 100644 backend/services/identity-service/identity-service/src/infrastructure/redis/redis.service.ts delete mode 100644 backend/services/identity-service/identity-service/src/main.ts delete mode 100644 backend/services/identity-service/identity-service/src/shared/decorators/current-user.decorator.ts delete mode 100644 backend/services/identity-service/identity-service/src/shared/decorators/public.decorator.ts delete mode 100644 backend/services/identity-service/identity-service/src/shared/exceptions/application.exception.ts delete mode 100644 backend/services/identity-service/identity-service/src/shared/exceptions/domain.exception.ts delete mode 100644 backend/services/identity-service/identity-service/src/shared/filters/domain-exception.filter.ts delete mode 100644 backend/services/identity-service/identity-service/src/shared/filters/global-exception.filter.ts delete mode 100644 backend/services/identity-service/identity-service/src/shared/guards/jwt-auth.guard.ts delete mode 100644 backend/services/identity-service/identity-service/src/shared/interceptors/transform.interceptor.ts delete mode 100644 backend/services/identity-service/identity-service/tsconfig.json diff --git a/backend/services/identity-service/Dockerfile b/backend/services/identity-service/Dockerfile index 21ba2455..f9d1b1da 100644 --- a/backend/services/identity-service/Dockerfile +++ b/backend/services/identity-service/Dockerfile @@ -4,7 +4,7 @@ FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ -COPY prisma ./prisma/ +COPY src/infrastructure/persistence/prisma ./src/infrastructure/persistence/prisma/ RUN npm ci @@ -20,7 +20,7 @@ WORKDIR /app COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/dist ./dist -COPY --from=builder /app/prisma ./prisma +COPY --from=builder /app/src/infrastructure/persistence/prisma ./src/infrastructure/persistence/prisma COPY --from=builder /app/package*.json ./ ENV NODE_ENV=production diff --git a/backend/services/identity-service/identity-service/.env.development b/backend/services/identity-service/identity-service/.env.development deleted file mode 100644 index e00b0d6b..00000000 --- a/backend/services/identity-service/identity-service/.env.development +++ /dev/null @@ -1,29 +0,0 @@ -# Database -DATABASE_URL="postgresql://postgres:password@localhost:5432/rwa_identity?schema=public" - -# JWT -JWT_SECRET="dev-jwt-secret-key" -JWT_ACCESS_EXPIRES_IN="2h" -JWT_REFRESH_EXPIRES_IN="30d" - -# Redis -REDIS_HOST="localhost" -REDIS_PORT=6379 -REDIS_PASSWORD="" -REDIS_DB=0 - -# Kafka -KAFKA_BROKERS="localhost:9092" -KAFKA_CLIENT_ID="identity-service" -KAFKA_GROUP_ID="identity-service-group" - -# SMS Service -SMS_API_URL="https://sms-api.example.com" -SMS_API_KEY="dev-sms-api-key" - -# App -APP_PORT=3000 -APP_ENV="development" - -# Blockchain Encryption -WALLET_ENCRYPTION_SALT="dev-wallet-salt" diff --git a/backend/services/identity-service/identity-service/.env.example b/backend/services/identity-service/identity-service/.env.example deleted file mode 100644 index cc9c2aa5..00000000 --- a/backend/services/identity-service/identity-service/.env.example +++ /dev/null @@ -1,29 +0,0 @@ -# Database -DATABASE_URL="postgresql://postgres:password@localhost:5432/rwa_identity?schema=public" - -# JWT -JWT_SECRET="your-super-secret-jwt-key-change-in-production" -JWT_ACCESS_EXPIRES_IN="2h" -JWT_REFRESH_EXPIRES_IN="30d" - -# Redis -REDIS_HOST="localhost" -REDIS_PORT=6379 -REDIS_PASSWORD="" -REDIS_DB=0 - -# Kafka -KAFKA_BROKERS="localhost:9092" -KAFKA_CLIENT_ID="identity-service" -KAFKA_GROUP_ID="identity-service-group" - -# SMS Service -SMS_API_URL="https://sms-api.example.com" -SMS_API_KEY="your-sms-api-key" - -# App -APP_PORT=3000 -APP_ENV="development" - -# Blockchain Encryption -WALLET_ENCRYPTION_SALT="rwa-wallet-salt-change-in-production" diff --git a/backend/services/identity-service/identity-service/.env.production b/backend/services/identity-service/identity-service/.env.production deleted file mode 100644 index 170f9bcb..00000000 --- a/backend/services/identity-service/identity-service/.env.production +++ /dev/null @@ -1,29 +0,0 @@ -# Database -DATABASE_URL="postgresql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:5432/${DB_NAME}?schema=public" - -# JWT -JWT_SECRET="${JWT_SECRET}" -JWT_ACCESS_EXPIRES_IN="2h" -JWT_REFRESH_EXPIRES_IN="30d" - -# Redis -REDIS_HOST="${REDIS_HOST}" -REDIS_PORT=6379 -REDIS_PASSWORD="${REDIS_PASSWORD}" -REDIS_DB=0 - -# Kafka -KAFKA_BROKERS="${KAFKA_BROKERS}" -KAFKA_CLIENT_ID="identity-service" -KAFKA_GROUP_ID="identity-service-group" - -# SMS Service -SMS_API_URL="${SMS_API_URL}" -SMS_API_KEY="${SMS_API_KEY}" - -# App -APP_PORT=3000 -APP_ENV="production" - -# Blockchain Encryption -WALLET_ENCRYPTION_SALT="${WALLET_ENCRYPTION_SALT}" diff --git a/backend/services/identity-service/identity-service/Dockerfile b/backend/services/identity-service/identity-service/Dockerfile deleted file mode 100644 index f9d1b1da..00000000 --- a/backend/services/identity-service/identity-service/Dockerfile +++ /dev/null @@ -1,30 +0,0 @@ -# Build stage -FROM node:20-alpine AS builder - -WORKDIR /app - -COPY package*.json ./ -COPY src/infrastructure/persistence/prisma ./src/infrastructure/persistence/prisma/ - -RUN npm ci - -COPY . . - -RUN npm run prisma:generate -RUN npm run build - -# Production stage -FROM node:20-alpine - -WORKDIR /app - -COPY --from=builder /app/node_modules ./node_modules -COPY --from=builder /app/dist ./dist -COPY --from=builder /app/src/infrastructure/persistence/prisma ./src/infrastructure/persistence/prisma -COPY --from=builder /app/package*.json ./ - -ENV NODE_ENV=production - -EXPOSE 3000 - -CMD ["npm", "run", "start:prod"] diff --git a/backend/services/identity-service/identity-service/README.md b/backend/services/identity-service/identity-service/README.md deleted file mode 100644 index 93c5c68b..00000000 --- a/backend/services/identity-service/identity-service/README.md +++ /dev/null @@ -1,124 +0,0 @@ -# Identity Service - -RWA用户身份上下文微服务 - 基于DDD架构的NestJS实现 - -## 技术栈 - -- **框架**: NestJS + TypeScript -- **ORM**: Prisma -- **消息队列**: Kafka -- **缓存**: Redis (ioredis) -- **区块链**: ethers.js + @scure/bip32 + bech32 - -## 项目结构 - -``` -src/ -├── api/ # 表现层 -│ ├── controllers/ # 控制器 -│ └── dto/ # 请求/响应DTO -├── application/ # 应用层 -│ ├── commands/ # 命令对象 -│ └── services/ # 应用服务 -├── domain/ # 领域层 -│ ├── aggregates/ # 聚合根 -│ ├── entities/ # 实体 -│ ├── events/ # 领域事件 -│ ├── repositories/ # 仓储接口 -│ ├── services/ # 领域服务 -│ └── value-objects/ # 值对象 -├── infrastructure/ # 基础设施层 -│ ├── persistence/ # 持久化 -│ ├── redis/ # Redis服务 -│ ├── kafka/ # Kafka事件发布 -│ └── external/ # 外部服务 -├── shared/ # 共享层 -│ ├── decorators/ # 装饰器 -│ ├── guards/ # 守卫 -│ ├── filters/ # 过滤器 -│ └── exceptions/ # 异常类 -└── config/ # 配置 -``` - -## 核心功能 - -- ✅ 用户账户自动创建(首次打开APP) -- ✅ 多设备管理与授权(最多5个设备) -- ✅ 三链钱包地址生成(KAVA/DST/BSC) -- ✅ 助记词生成与加密存储 -- ✅ 序列号+助记词恢复账户 -- ✅ 序列号+手机号恢复账户 -- ✅ KYC实名认证 -- ✅ 推荐码生成与验证 -- ✅ Token自动刷新机制 - -## 快速开始 - -### 1. 安装依赖 - -```bash -npm install -``` - -### 2. 配置环境变量 - -```bash -cp .env.example .env -# 编辑 .env 文件配置数据库等信息 -``` - -### 3. 初始化数据库 - -```bash -npm run prisma:generate -npm run prisma:migrate -``` - -### 4. 启动服务 - -```bash -# 开发模式 -npm run start:dev - -# 生产模式 -npm run build -npm run start:prod -``` - -### 5. Docker部署 - -```bash -docker-compose up -d -``` - -## API文档 - -启动服务后访问: http://localhost:3000/api/docs - -## 主要API - -| 方法 | 路径 | 说明 | -|------|------|------| -| POST | /user/auto-create | 自动创建账户 | -| POST | /user/recover-by-mnemonic | 助记词恢复 | -| POST | /user/recover-by-phone | 手机号恢复 | -| POST | /user/auto-login | 自动登录 | -| GET | /user/my-profile | 我的资料 | -| GET | /user/my-devices | 我的设备 | -| POST | /user/bind-phone | 绑定手机号 | -| POST | /user/submit-kyc | 提交KYC | - -## 领域不变式 - -1. 手机号在系统内唯一(可为空) -2. 账户序列号全局唯一且递增 -3. 每个账户最多5个设备同时登录 -4. KYC认证通过后身份信息不可修改 -5. 每个区块链地址只能绑定一个账户 -6. 推荐人序列号一旦设置终生不可修改 -7. 助记词必须加密存储,只在创建时返回一次 -8. 三条链的钱包地址必须从同一个助记词派生 - -## License - -Proprietary diff --git a/backend/services/identity-service/identity-service/database/init.sql b/backend/services/identity-service/identity-service/database/init.sql deleted file mode 100644 index f713b066..00000000 --- a/backend/services/identity-service/identity-service/database/init.sql +++ /dev/null @@ -1,7 +0,0 @@ --- ============================================ --- Identity Context 数据库初始化 (PostgreSQL) --- ============================================ - --- 初始化账户序列号生成器 -INSERT INTO account_sequence_generator (id, current_sequence) VALUES (1, 0) -ON CONFLICT (id) DO NOTHING; diff --git a/backend/services/identity-service/identity-service/docker-compose.yml b/backend/services/identity-service/identity-service/docker-compose.yml deleted file mode 100644 index 6e8f77a9..00000000 --- a/backend/services/identity-service/identity-service/docker-compose.yml +++ /dev/null @@ -1,69 +0,0 @@ -version: '3.8' - -services: - identity-service: - build: . - ports: - - "3000:3000" - environment: - - DATABASE_URL=postgresql://postgres:password@postgres:5432/rwa_identity?schema=public - - JWT_SECRET=your-super-secret-jwt-key-change-in-production - - JWT_ACCESS_EXPIRES_IN=2h - - JWT_REFRESH_EXPIRES_IN=30d - - REDIS_HOST=redis - - REDIS_PORT=6379 - - KAFKA_BROKERS=kafka:9092 - - APP_PORT=3000 - - APP_ENV=production - depends_on: - postgres: - condition: service_healthy - redis: - condition: service_started - kafka: - condition: service_started - - postgres: - image: postgres:16-alpine - environment: - - POSTGRES_USER=postgres - - POSTGRES_PASSWORD=password - - POSTGRES_DB=rwa_identity - ports: - - "5432:5432" - volumes: - - postgres_data:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres"] - interval: 5s - timeout: 5s - retries: 10 - - redis: - image: redis:7-alpine - ports: - - "6379:6379" - volumes: - - redis_data:/data - - zookeeper: - image: confluentinc/cp-zookeeper:7.5.0 - environment: - ZOOKEEPER_CLIENT_PORT: 2181 - ZOOKEEPER_TICK_TIME: 2000 - - kafka: - image: confluentinc/cp-kafka:7.5.0 - depends_on: - - zookeeper - ports: - - "9092:9092" - environment: - KAFKA_BROKER_ID: 1 - KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 - KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092 - KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 - -volumes: - postgres_data: - redis_data: diff --git a/backend/services/identity-service/identity-service/nest-cli.json b/backend/services/identity-service/identity-service/nest-cli.json deleted file mode 100644 index f5e93169..00000000 --- a/backend/services/identity-service/identity-service/nest-cli.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/nest-cli", - "collection": "@nestjs/schematics", - "sourceRoot": "src", - "compilerOptions": { - "deleteOutDir": true, - "plugins": [ - { - "name": "@nestjs/swagger", - "options": { - "classValidatorShim": true, - "introspectComments": true - } - } - ] - } -} diff --git a/backend/services/identity-service/identity-service/package.json b/backend/services/identity-service/identity-service/package.json deleted file mode 100644 index fc9db1c0..00000000 --- a/backend/services/identity-service/identity-service/package.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "name": "identity-service", - "version": "1.0.0", - "description": "RWA Identity & User Context Service", - "author": "RWA Team", - "private": true, - "license": "UNLICENSED", - "prisma": { - "schema": "src/infrastructure/persistence/prisma/schema.prisma" - }, - "scripts": { - "build": "nest build", - "format": "prettier --write \"src/**/*.ts\" \"test/**/*.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", - "test": "jest", - "test:watch": "jest --watch", - "test:cov": "jest --coverage", - "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", - "test:e2e": "jest --config ./test/jest-e2e.json", - "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/jwt": "^10.2.0", - "@nestjs/microservices": "^10.0.0", - "@nestjs/platform-express": "^10.0.0", - "@nestjs/swagger": "^7.1.17", - "@prisma/client": "^5.7.0", - "@scure/bip32": "^1.3.2", - "@scure/bip39": "^1.2.1", - "bech32": "^2.0.0", - "class-transformer": "^0.5.1", - "class-validator": "^0.14.0", - "ethers": "^6.9.0", - "ioredis": "^5.3.2", - "kafkajs": "^2.2.4", - "reflect-metadata": "^0.1.13", - "rxjs": "^7.8.1", - "uuid": "^9.0.0" - }, - "devDependencies": { - "@nestjs/cli": "^10.0.0", - "@nestjs/schematics": "^10.0.0", - "@nestjs/testing": "^10.0.0", - "@types/express": "^4.17.17", - "@types/jest": "^29.5.2", - "@types/node": "^20.3.1", - "@types/uuid": "^9.0.0", - "@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", - "jest": "^29.5.0", - "prettier": "^3.0.0", - "prisma": "^5.7.0", - "source-map-support": "^0.5.21", - "ts-jest": "^29.1.0", - "ts-loader": "^9.4.3", - "ts-node": "^10.9.1", - "tsconfig-paths": "^4.2.0", - "typescript": "^5.1.3" - }, - "jest": { - "moduleFileExtensions": ["js", "json", "ts"], - "rootDir": "src", - "testRegex": ".*\\.spec\\.ts$", - "transform": { - "^.+\\.(t|j)s$": "ts-jest" - }, - "collectCoverageFrom": ["**/*.(t|j)s"], - "coverageDirectory": "../coverage", - "testEnvironment": "node", - "moduleNameMapper": { - "^@/(.*)$": "/$1" - } - } -} diff --git a/backend/services/identity-service/identity-service/src/api/api.module.ts b/backend/services/identity-service/identity-service/src/api/api.module.ts deleted file mode 100644 index ab8c73ee..00000000 --- a/backend/services/identity-service/identity-service/src/api/api.module.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Module } from '@nestjs/common'; -import { UserAccountController } from './controllers/user-account.controller'; -import { AuthController } from './controllers/auth.controller'; -import { ApplicationModule } from '@/application/application.module'; - -@Module({ - imports: [ApplicationModule], - controllers: [UserAccountController, AuthController], -}) -export class ApiModule {} diff --git a/backend/services/identity-service/identity-service/src/api/controllers/auth.controller.ts b/backend/services/identity-service/identity-service/src/api/controllers/auth.controller.ts deleted file mode 100644 index 8664c668..00000000 --- a/backend/services/identity-service/identity-service/src/api/controllers/auth.controller.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Controller, Post, Body } from '@nestjs/common'; -import { ApiTags, ApiOperation } from '@nestjs/swagger'; -import { UserApplicationService } from '@/application/services/user-application.service'; -import { Public } from '@/shared/guards/jwt-auth.guard'; -import { AutoLoginCommand } from '@/application/commands'; -import { AutoLoginDto } from '@/api/dto'; - -@ApiTags('Auth') -@Controller('auth') -export class AuthController { - constructor(private readonly userService: UserApplicationService) {} - - @Public() - @Post('refresh') - @ApiOperation({ summary: 'Token刷新' }) - async refresh(@Body() dto: AutoLoginDto) { - return this.userService.autoLogin(new AutoLoginCommand(dto.refreshToken, dto.deviceId)); - } -} diff --git a/backend/services/identity-service/identity-service/src/api/controllers/user-account.controller.ts b/backend/services/identity-service/identity-service/src/api/controllers/user-account.controller.ts deleted file mode 100644 index 520095cc..00000000 --- a/backend/services/identity-service/identity-service/src/api/controllers/user-account.controller.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { Controller, Post, Get, Put, Body, Param, UseGuards, Headers } from '@nestjs/common'; -import { ApiTags, ApiOperation, ApiBearerAuth, ApiResponse } from '@nestjs/swagger'; -import { UserApplicationService } from '@/application/services/user-application.service'; -import { JwtAuthGuard, Public, CurrentUser, CurrentUserData } from '@/shared/guards/jwt-auth.guard'; -import { - AutoCreateAccountCommand, RecoverByMnemonicCommand, RecoverByPhoneCommand, - AutoLoginCommand, RegisterCommand, LoginCommand, BindPhoneNumberCommand, - UpdateProfileCommand, SubmitKYCCommand, RemoveDeviceCommand, SendSmsCodeCommand, - GetMyProfileQuery, GetMyDevicesQuery, GetUserByReferralCodeQuery, -} from '@/application/commands'; -import { - AutoCreateAccountDto, RecoverByMnemonicDto, RecoverByPhoneDto, AutoLoginDto, - SendSmsCodeDto, RegisterDto, LoginDto, BindPhoneDto, UpdateProfileDto, - BindWalletDto, SubmitKYCDto, RemoveDeviceDto, - AutoCreateAccountResponseDto, RecoverAccountResponseDto, LoginResponseDto, - UserProfileResponseDto, DeviceResponseDto, -} from '@/api/dto'; - -@ApiTags('User') -@Controller('user') -@UseGuards(JwtAuthGuard) -export class UserAccountController { - constructor(private readonly userService: UserApplicationService) {} - - @Public() - @Post('auto-create') - @ApiOperation({ summary: '自动创建账户(首次打开APP)' }) - @ApiResponse({ status: 200, type: AutoCreateAccountResponseDto }) - async autoCreate(@Body() dto: AutoCreateAccountDto) { - return this.userService.autoCreateAccount( - new AutoCreateAccountCommand( - dto.deviceId, dto.deviceName, dto.inviterReferralCode, - dto.provinceCode, dto.cityCode, - ), - ); - } - - @Public() - @Post('recover-by-mnemonic') - @ApiOperation({ summary: '用序列号+助记词恢复账户' }) - @ApiResponse({ status: 200, type: RecoverAccountResponseDto }) - async recoverByMnemonic(@Body() dto: RecoverByMnemonicDto) { - return this.userService.recoverByMnemonic( - new RecoverByMnemonicCommand( - dto.accountSequence, dto.mnemonic, dto.newDeviceId, dto.deviceName, - ), - ); - } - - @Public() - @Post('recover-by-phone') - @ApiOperation({ summary: '用序列号+手机号恢复账户' }) - @ApiResponse({ status: 200, type: RecoverAccountResponseDto }) - async recoverByPhone(@Body() dto: RecoverByPhoneDto) { - return this.userService.recoverByPhone( - new RecoverByPhoneCommand( - dto.accountSequence, dto.phoneNumber, dto.smsCode, - dto.newDeviceId, dto.deviceName, - ), - ); - } - - @Public() - @Post('auto-login') - @ApiOperation({ summary: '自动登录(Token刷新)' }) - @ApiResponse({ status: 200, type: LoginResponseDto }) - async autoLogin(@Body() dto: AutoLoginDto) { - return this.userService.autoLogin( - new AutoLoginCommand(dto.refreshToken, dto.deviceId), - ); - } - - @Public() - @Post('send-sms-code') - @ApiOperation({ summary: '发送短信验证码' }) - async sendSmsCode(@Body() dto: SendSmsCodeDto) { - await this.userService.sendSmsCode(new SendSmsCodeCommand(dto.phoneNumber, dto.type)); - return { message: '验证码已发送' }; - } - - @Public() - @Post('register') - @ApiOperation({ summary: '用户注册(手机号)' }) - @ApiResponse({ status: 200, type: LoginResponseDto }) - async register(@Body() dto: RegisterDto) { - return this.userService.register( - new RegisterCommand( - dto.phoneNumber, dto.smsCode, dto.deviceId, - dto.provinceCode, dto.cityCode, dto.deviceName, dto.inviterReferralCode, - ), - ); - } - - @Public() - @Post('login') - @ApiOperation({ summary: '用户登录(手机号)' }) - @ApiResponse({ status: 200, type: LoginResponseDto }) - async login(@Body() dto: LoginDto) { - return this.userService.login( - new LoginCommand(dto.phoneNumber, dto.smsCode, dto.deviceId), - ); - } - - @Post('bind-phone') - @ApiBearerAuth() - @ApiOperation({ summary: '绑定手机号' }) - async bindPhone(@CurrentUser() user: CurrentUserData, @Body() dto: BindPhoneDto) { - await this.userService.bindPhoneNumber( - new BindPhoneNumberCommand(user.userId, dto.phoneNumber, dto.smsCode), - ); - return { message: '绑定成功' }; - } - - @Get('my-profile') - @ApiBearerAuth() - @ApiOperation({ summary: '查询我的资料' }) - @ApiResponse({ status: 200, type: UserProfileResponseDto }) - async getMyProfile(@CurrentUser() user: CurrentUserData) { - return this.userService.getMyProfile(new GetMyProfileQuery(user.userId)); - } - - @Put('update-profile') - @ApiBearerAuth() - @ApiOperation({ summary: '更新用户资料' }) - async updateProfile(@CurrentUser() user: CurrentUserData, @Body() dto: UpdateProfileDto) { - await this.userService.updateProfile( - new UpdateProfileCommand(user.userId, dto.nickname, dto.avatarUrl, dto.address), - ); - return { message: '更新成功' }; - } - - @Post('submit-kyc') - @ApiBearerAuth() - @ApiOperation({ summary: '提交KYC认证' }) - async submitKYC(@CurrentUser() user: CurrentUserData, @Body() dto: SubmitKYCDto) { - await this.userService.submitKYC( - new SubmitKYCCommand( - user.userId, dto.realName, dto.idCardNumber, - dto.idCardFrontUrl, dto.idCardBackUrl, - ), - ); - return { message: '提交成功' }; - } - - @Get('my-devices') - @ApiBearerAuth() - @ApiOperation({ summary: '查看我的设备列表' }) - @ApiResponse({ status: 200, type: [DeviceResponseDto] }) - async getMyDevices(@CurrentUser() user: CurrentUserData) { - return this.userService.getMyDevices(new GetMyDevicesQuery(user.userId, user.deviceId)); - } - - @Post('remove-device') - @ApiBearerAuth() - @ApiOperation({ summary: '移除设备' }) - async removeDevice(@CurrentUser() user: CurrentUserData, @Body() dto: RemoveDeviceDto) { - await this.userService.removeDevice( - new RemoveDeviceCommand(user.userId, user.deviceId, dto.deviceId), - ); - return { message: '移除成功' }; - } - - @Public() - @Get('by-referral-code/:code') - @ApiOperation({ summary: '根据推荐码查询用户' }) - async getByReferralCode(@Param('code') code: string) { - return this.userService.getUserByReferralCode(new GetUserByReferralCodeQuery(code)); - } -} diff --git a/backend/services/identity-service/identity-service/src/api/dto/index.ts b/backend/services/identity-service/identity-service/src/api/dto/index.ts deleted file mode 100644 index 69d8003c..00000000 --- a/backend/services/identity-service/identity-service/src/api/dto/index.ts +++ /dev/null @@ -1,182 +0,0 @@ -// Request DTOs -export * from './request'; - -// Response DTOs -export * from './response'; - -// 其他通用DTOs -import { IsString, IsOptional, IsNotEmpty, Matches, IsEnum, IsNumber } from 'class-validator'; -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; - -export class AutoLoginDto { - @ApiProperty() - @IsString() - @IsNotEmpty() - refreshToken: string; - - @ApiProperty() - @IsString() - @IsNotEmpty() - deviceId: string; -} - -export class SendSmsCodeDto { - @ApiProperty({ example: '13800138000' }) - @IsString() - @Matches(/^1[3-9]\d{9}$/, { message: '手机号格式错误' }) - phoneNumber: string; - - @ApiProperty({ enum: ['REGISTER', 'LOGIN', 'BIND', 'RECOVER'] }) - @IsEnum(['REGISTER', 'LOGIN', 'BIND', 'RECOVER']) - type: 'REGISTER' | 'LOGIN' | 'BIND' | 'RECOVER'; -} - -export class RegisterDto { - @ApiProperty({ example: '13800138000' }) - @IsString() - @Matches(/^1[3-9]\d{9}$/, { message: '手机号格式错误' }) - phoneNumber: string; - - @ApiProperty({ example: '123456' }) - @IsString() - @Matches(/^\d{6}$/, { message: '验证码格式错误' }) - smsCode: string; - - @ApiProperty() - @IsString() - @IsNotEmpty() - deviceId: string; - - @ApiProperty() - @IsString() - @IsNotEmpty() - provinceCode: string; - - @ApiProperty() - @IsString() - @IsNotEmpty() - cityCode: string; - - @ApiPropertyOptional() - @IsOptional() - @IsString() - deviceName?: string; - - @ApiPropertyOptional() - @IsOptional() - @IsString() - inviterReferralCode?: string; -} - -export class LoginDto { - @ApiProperty({ example: '13800138000' }) - @IsString() - @Matches(/^1[3-9]\d{9}$/, { message: '手机号格式错误' }) - phoneNumber: string; - - @ApiProperty({ example: '123456' }) - @IsString() - @Matches(/^\d{6}$/, { message: '验证码格式错误' }) - smsCode: string; - - @ApiProperty() - @IsString() - @IsNotEmpty() - deviceId: string; -} - -export class UpdateProfileDto { - @ApiPropertyOptional() - @IsOptional() - @IsString() - nickname?: string; - - @ApiPropertyOptional() - @IsOptional() - @IsString() - avatarUrl?: string; - - @ApiPropertyOptional() - @IsOptional() - @IsString() - address?: string; -} - -export class BindWalletDto { - @ApiProperty({ enum: ['KAVA', 'DST', 'BSC'] }) - @IsEnum(['KAVA', 'DST', 'BSC']) - chainType: string; - - @ApiProperty() - @IsString() - @IsNotEmpty() - address: string; -} - -export class RemoveDeviceDto { - @ApiProperty() - @IsString() - @IsNotEmpty() - deviceId: string; -} - -// Response DTOs -export class AutoCreateAccountResponseDto { - @ApiProperty() - userId: string; - - @ApiProperty() - accountSequence: number; - - @ApiProperty() - referralCode: string; - - @ApiProperty({ description: '助记词(仅返回一次,请妥善保管)' }) - mnemonic: string; - - @ApiProperty() - walletAddresses: { kava: string; dst: string; bsc: string }; - - @ApiProperty() - accessToken: string; - - @ApiProperty() - refreshToken: string; -} - -export class RecoverAccountResponseDto { - @ApiProperty() - userId: string; - - @ApiProperty() - accountSequence: number; - - @ApiProperty() - nickname: string; - - @ApiProperty({ nullable: true }) - avatarUrl: string | null; - - @ApiProperty() - referralCode: string; - - @ApiProperty() - accessToken: string; - - @ApiProperty() - refreshToken: string; -} - -export class LoginResponseDto { - @ApiProperty() - userId: string; - - @ApiProperty() - accountSequence: number; - - @ApiProperty() - accessToken: string; - - @ApiProperty() - refreshToken: string; -} diff --git a/backend/services/identity-service/identity-service/src/api/dto/request/auto-create-account.dto.ts b/backend/services/identity-service/identity-service/src/api/dto/request/auto-create-account.dto.ts deleted file mode 100644 index 82ca0f31..00000000 --- a/backend/services/identity-service/identity-service/src/api/dto/request/auto-create-account.dto.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { IsString, IsOptional, IsNotEmpty, Matches } from 'class-validator'; -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; - -export class AutoCreateAccountDto { - @ApiProperty({ example: '550e8400-e29b-41d4-a716-446655440000' }) - @IsString() - @IsNotEmpty() - deviceId: string; - - @ApiPropertyOptional({ example: 'iPhone 15 Pro' }) - @IsOptional() - @IsString() - deviceName?: string; - - @ApiPropertyOptional({ example: 'ABC123' }) - @IsOptional() - @IsString() - @Matches(/^[A-Z0-9]{6}$/, { message: '推荐码格式错误' }) - inviterReferralCode?: string; - - @ApiPropertyOptional() - @IsOptional() - @IsString() - provinceCode?: string; - - @ApiPropertyOptional() - @IsOptional() - @IsString() - cityCode?: string; -} diff --git a/backend/services/identity-service/identity-service/src/api/dto/request/bind-phone.dto.ts b/backend/services/identity-service/identity-service/src/api/dto/request/bind-phone.dto.ts deleted file mode 100644 index 58a5b5c7..00000000 --- a/backend/services/identity-service/identity-service/src/api/dto/request/bind-phone.dto.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { IsString, Matches } from 'class-validator'; -import { ApiProperty } from '@nestjs/swagger'; - -export class BindPhoneDto { - @ApiProperty({ example: '13800138000' }) - @IsString() - @Matches(/^1[3-9]\d{9}$/, { message: '手机号格式错误' }) - phoneNumber: string; - - @ApiProperty({ example: '123456' }) - @IsString() - @Matches(/^\d{6}$/, { message: '验证码格式错误' }) - smsCode: string; -} diff --git a/backend/services/identity-service/identity-service/src/api/dto/request/index.ts b/backend/services/identity-service/identity-service/src/api/dto/request/index.ts deleted file mode 100644 index 16fffe6a..00000000 --- a/backend/services/identity-service/identity-service/src/api/dto/request/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './auto-create-account.dto'; -export * from './recover-by-mnemonic.dto'; -export * from './recover-by-phone.dto'; -export * from './bind-phone.dto'; -export * from './submit-kyc.dto'; diff --git a/backend/services/identity-service/identity-service/src/api/dto/request/recover-by-mnemonic.dto.ts b/backend/services/identity-service/identity-service/src/api/dto/request/recover-by-mnemonic.dto.ts deleted file mode 100644 index b11209a3..00000000 --- a/backend/services/identity-service/identity-service/src/api/dto/request/recover-by-mnemonic.dto.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { IsString, IsOptional, IsNotEmpty, IsNumber } from 'class-validator'; -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; - -export class RecoverByMnemonicDto { - @ApiProperty({ example: 10001 }) - @IsNumber() - accountSequence: number; - - @ApiProperty({ example: 'abandon ability able about above absent absorb abstract absurd abuse access accident' }) - @IsString() - @IsNotEmpty() - mnemonic: string; - - @ApiProperty() - @IsString() - @IsNotEmpty() - newDeviceId: string; - - @ApiPropertyOptional() - @IsOptional() - @IsString() - deviceName?: string; -} diff --git a/backend/services/identity-service/identity-service/src/api/dto/request/recover-by-phone.dto.ts b/backend/services/identity-service/identity-service/src/api/dto/request/recover-by-phone.dto.ts deleted file mode 100644 index 1a06f423..00000000 --- a/backend/services/identity-service/identity-service/src/api/dto/request/recover-by-phone.dto.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { IsString, IsOptional, IsNotEmpty, IsNumber, Matches } from 'class-validator'; -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; - -export class RecoverByPhoneDto { - @ApiProperty({ example: 10001 }) - @IsNumber() - accountSequence: number; - - @ApiProperty({ example: '13800138000' }) - @IsString() - @Matches(/^1[3-9]\d{9}$/, { message: '手机号格式错误' }) - phoneNumber: string; - - @ApiProperty({ example: '123456' }) - @IsString() - @Matches(/^\d{6}$/, { message: '验证码格式错误' }) - smsCode: string; - - @ApiProperty() - @IsString() - @IsNotEmpty() - newDeviceId: string; - - @ApiPropertyOptional() - @IsOptional() - @IsString() - deviceName?: string; -} diff --git a/backend/services/identity-service/identity-service/src/api/dto/request/submit-kyc.dto.ts b/backend/services/identity-service/identity-service/src/api/dto/request/submit-kyc.dto.ts deleted file mode 100644 index 343025f4..00000000 --- a/backend/services/identity-service/identity-service/src/api/dto/request/submit-kyc.dto.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { IsString, IsNotEmpty, Matches } from 'class-validator'; -import { ApiProperty } from '@nestjs/swagger'; - -export class SubmitKycDto { - @ApiProperty({ example: '张三' }) - @IsString() - @IsNotEmpty() - realName: string; - - @ApiProperty({ example: '110101199001011234' }) - @IsString() - @Matches(/^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[0-9Xx]$/, { message: '身份证号格式错误' }) - idCardNumber: string; - - @ApiProperty() - @IsString() - @IsNotEmpty() - idCardFrontUrl: string; - - @ApiProperty() - @IsString() - @IsNotEmpty() - idCardBackUrl: string; -} diff --git a/backend/services/identity-service/identity-service/src/api/dto/response/device.dto.ts b/backend/services/identity-service/identity-service/src/api/dto/response/device.dto.ts deleted file mode 100644 index 8d2fbdfe..00000000 --- a/backend/services/identity-service/identity-service/src/api/dto/response/device.dto.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; - -export class DeviceDto { - @ApiProperty() - deviceId: string; - - @ApiProperty() - deviceName: string; - - @ApiProperty() - addedAt: Date; - - @ApiProperty() - lastActiveAt: Date; - - @ApiProperty() - isCurrent: boolean; -} diff --git a/backend/services/identity-service/identity-service/src/api/dto/response/index.ts b/backend/services/identity-service/identity-service/src/api/dto/response/index.ts deleted file mode 100644 index 1e8628d1..00000000 --- a/backend/services/identity-service/identity-service/src/api/dto/response/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './user-profile.dto'; -export * from './device.dto'; diff --git a/backend/services/identity-service/identity-service/src/api/dto/response/user-profile.dto.ts b/backend/services/identity-service/identity-service/src/api/dto/response/user-profile.dto.ts deleted file mode 100644 index ab311e3a..00000000 --- a/backend/services/identity-service/identity-service/src/api/dto/response/user-profile.dto.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; - -export class WalletAddressDto { - @ApiProperty() - chainType: string; - - @ApiProperty() - address: string; -} - -export class KycInfoDto { - @ApiProperty() - realName: string; - - @ApiProperty() - idCardNumber: string; -} - -export class UserProfileDto { - @ApiProperty() - userId: string; - - @ApiProperty() - accountSequence: number; - - @ApiProperty({ nullable: true }) - phoneNumber: string | null; - - @ApiProperty() - nickname: string; - - @ApiProperty({ nullable: true }) - avatarUrl: string | null; - - @ApiProperty() - referralCode: string; - - @ApiProperty() - province: string; - - @ApiProperty() - city: string; - - @ApiProperty({ nullable: true }) - address: string | null; - - @ApiProperty({ type: [WalletAddressDto] }) - walletAddresses: WalletAddressDto[]; - - @ApiProperty() - kycStatus: string; - - @ApiProperty({ type: KycInfoDto, nullable: true }) - kycInfo: KycInfoDto | null; - - @ApiProperty() - status: string; - - @ApiProperty() - registeredAt: Date; - - @ApiProperty({ nullable: true }) - lastLoginAt: Date | null; -} diff --git a/backend/services/identity-service/identity-service/src/api/validators/phone.validator.ts b/backend/services/identity-service/identity-service/src/api/validators/phone.validator.ts deleted file mode 100644 index ba383d59..00000000 --- a/backend/services/identity-service/identity-service/src/api/validators/phone.validator.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments, registerDecorator, ValidationOptions } from 'class-validator'; - -@ValidatorConstraint({ name: 'isChinesePhone', async: false }) -export class IsChinesePhoneConstraint implements ValidatorConstraintInterface { - validate(phone: string, args: ValidationArguments): boolean { - return /^1[3-9]\d{9}$/.test(phone); - } - - defaultMessage(args: ValidationArguments): string { - return '手机号格式错误'; - } -} - -export function IsChinesePhone(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - registerDecorator({ - target: object.constructor, - propertyName: propertyName, - options: validationOptions, - constraints: [], - validator: IsChinesePhoneConstraint, - }); - }; -} - -@ValidatorConstraint({ name: 'isChineseIdCard', async: false }) -export class IsChineseIdCardConstraint implements ValidatorConstraintInterface { - validate(idCard: string, args: ValidationArguments): boolean { - return /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[0-9Xx]$/.test(idCard); - } - - defaultMessage(args: ValidationArguments): string { - return '身份证号格式错误'; - } -} - -export function IsChineseIdCard(validationOptions?: ValidationOptions) { - return function (object: Object, propertyName: string) { - registerDecorator({ - target: object.constructor, - propertyName: propertyName, - options: validationOptions, - constraints: [], - validator: IsChineseIdCardConstraint, - }); - }; -} diff --git a/backend/services/identity-service/identity-service/src/app.module.ts b/backend/services/identity-service/identity-service/src/app.module.ts deleted file mode 100644 index 9f274d2e..00000000 --- a/backend/services/identity-service/identity-service/src/app.module.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { Module, Global } from '@nestjs/common'; -import { ConfigModule, ConfigService } from '@nestjs/config'; -import { JwtModule } from '@nestjs/jwt'; -import { APP_FILTER, APP_INTERCEPTOR, APP_GUARD } from '@nestjs/core'; - -// Config -import { appConfig, databaseConfig, jwtConfig, redisConfig, kafkaConfig, smsConfig, walletConfig } from '@/config'; - -// Controllers -import { UserAccountController } from '@/api/controllers/user-account.controller'; - -// Application Services -import { UserApplicationService } from '@/application/services/user-application.service'; -import { TokenService } from '@/application/services/token.service'; - -// Domain Services -import { - AccountSequenceGeneratorService, UserValidatorService, WalletGeneratorService, -} from '@/domain/services'; -import { USER_ACCOUNT_REPOSITORY } from '@/domain/repositories/user-account.repository.interface'; - -// Infrastructure -import { PrismaService } from '@/infrastructure/persistence/prisma/prisma.service'; -import { UserAccountRepositoryImpl } from '@/infrastructure/persistence/repositories/user-account.repository.impl'; -import { RedisService } from '@/infrastructure/redis/redis.service'; -import { EventPublisherService } from '@/infrastructure/kafka/event-publisher.service'; -import { SmsService } from '@/infrastructure/external/sms/sms.service'; - -// Shared -import { GlobalExceptionFilter, TransformInterceptor } from '@/shared/filters/global-exception.filter'; -import { JwtAuthGuard } from '@/shared/guards/jwt-auth.guard'; - -// ============ Infrastructure Module ============ -@Global() -@Module({ - providers: [PrismaService, RedisService, EventPublisherService, SmsService], - exports: [PrismaService, RedisService, EventPublisherService, SmsService], -}) -export class InfrastructureModule {} - -// ============ Domain Module ============ -@Module({ - imports: [InfrastructureModule], - providers: [ - { provide: USER_ACCOUNT_REPOSITORY, useClass: UserAccountRepositoryImpl }, - AccountSequenceGeneratorService, - UserValidatorService, - WalletGeneratorService, - ], - exports: [ - USER_ACCOUNT_REPOSITORY, - AccountSequenceGeneratorService, - UserValidatorService, - WalletGeneratorService, - ], -}) -export class DomainModule {} - -// ============ Application Module ============ -@Module({ - imports: [DomainModule, InfrastructureModule], - providers: [UserApplicationService, TokenService], - exports: [UserApplicationService, TokenService], -}) -export class ApplicationModule {} - -// ============ API Module ============ -@Module({ - imports: [ApplicationModule], - controllers: [UserAccountController], -}) -export class ApiModule {} - -// ============ App Module ============ -@Module({ - imports: [ - ConfigModule.forRoot({ - isGlobal: true, - load: [appConfig, databaseConfig, jwtConfig, redisConfig, kafkaConfig, smsConfig, walletConfig], - }), - JwtModule.registerAsync({ - global: true, - inject: [ConfigService], - useFactory: (configService: ConfigService) => ({ - secret: configService.get('JWT_SECRET'), - signOptions: { expiresIn: configService.get('JWT_ACCESS_EXPIRES_IN', '2h') }, - }), - }), - InfrastructureModule, - DomainModule, - ApplicationModule, - ApiModule, - ], - providers: [ - { provide: APP_FILTER, useClass: GlobalExceptionFilter }, - { provide: APP_INTERCEPTOR, useClass: TransformInterceptor }, - { provide: APP_GUARD, useClass: JwtAuthGuard }, - ], -}) -export class AppModule {} diff --git a/backend/services/identity-service/identity-service/src/application/application.module.ts b/backend/services/identity-service/identity-service/src/application/application.module.ts deleted file mode 100644 index f7a3f643..00000000 --- a/backend/services/identity-service/identity-service/src/application/application.module.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Module } from '@nestjs/common'; -import { UserApplicationService } from './services/user-application.service'; -import { TokenService } from './services/token.service'; -import { AutoCreateAccountHandler } from './commands/auto-create-account/auto-create-account.handler'; -import { RecoverByMnemonicHandler } from './commands/recover-by-mnemonic/recover-by-mnemonic.handler'; -import { RecoverByPhoneHandler } from './commands/recover-by-phone/recover-by-phone.handler'; -import { BindPhoneHandler } from './commands/bind-phone/bind-phone.handler'; -import { GetMyProfileHandler } from './queries/get-my-profile/get-my-profile.handler'; -import { GetMyDevicesHandler } from './queries/get-my-devices/get-my-devices.handler'; -import { DomainModule } from '@/domain/domain.module'; -import { InfrastructureModule } from '@/infrastructure/infrastructure.module'; - -@Module({ - imports: [DomainModule, InfrastructureModule], - providers: [ - UserApplicationService, - TokenService, - AutoCreateAccountHandler, - RecoverByMnemonicHandler, - RecoverByPhoneHandler, - BindPhoneHandler, - GetMyProfileHandler, - GetMyDevicesHandler, - ], - exports: [ - UserApplicationService, - TokenService, - AutoCreateAccountHandler, - RecoverByMnemonicHandler, - RecoverByPhoneHandler, - BindPhoneHandler, - GetMyProfileHandler, - GetMyDevicesHandler, - ], -}) -export class ApplicationModule {} diff --git a/backend/services/identity-service/identity-service/src/application/commands/auto-create-account/auto-create-account.command.ts b/backend/services/identity-service/identity-service/src/application/commands/auto-create-account/auto-create-account.command.ts deleted file mode 100644 index 08ded2df..00000000 --- a/backend/services/identity-service/identity-service/src/application/commands/auto-create-account/auto-create-account.command.ts +++ /dev/null @@ -1,9 +0,0 @@ -export class AutoCreateAccountCommand { - constructor( - public readonly deviceId: string, - public readonly deviceName?: string, - public readonly inviterReferralCode?: string, - public readonly provinceCode?: string, - public readonly cityCode?: string, - ) {} -} diff --git a/backend/services/identity-service/identity-service/src/application/commands/auto-create-account/auto-create-account.handler.ts b/backend/services/identity-service/identity-service/src/application/commands/auto-create-account/auto-create-account.handler.ts deleted file mode 100644 index 54a2ebc6..00000000 --- a/backend/services/identity-service/identity-service/src/application/commands/auto-create-account/auto-create-account.handler.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { Injectable, Inject } from '@nestjs/common'; -import { AutoCreateAccountCommand } from './auto-create-account.command'; -import { UserAccountRepository, USER_ACCOUNT_REPOSITORY } from '@/domain/repositories/user-account.repository.interface'; -import { UserAccount } from '@/domain/aggregates/user-account/user-account.aggregate'; -import { AccountSequenceGeneratorService, UserValidatorService, WalletGeneratorService } from '@/domain/services'; -import { ReferralCode, AccountSequence, ProvinceCode, CityCode, ChainType } from '@/domain/value-objects'; -import { TokenService } from '@/application/services/token.service'; -import { EventPublisherService } from '@/infrastructure/kafka/event-publisher.service'; -import { ApplicationError } from '@/shared/exceptions/domain.exception'; -import { AutoCreateAccountResult } from '../index'; - -@Injectable() -export class AutoCreateAccountHandler { - constructor( - @Inject(USER_ACCOUNT_REPOSITORY) - private readonly userRepository: UserAccountRepository, - private readonly sequenceGenerator: AccountSequenceGeneratorService, - private readonly validatorService: UserValidatorService, - private readonly walletGenerator: WalletGeneratorService, - private readonly tokenService: TokenService, - private readonly eventPublisher: EventPublisherService, - ) {} - - async execute(command: AutoCreateAccountCommand): Promise { - const deviceValidation = await this.validatorService.validateDeviceId(command.deviceId); - if (!deviceValidation.isValid) throw new ApplicationError(deviceValidation.errorMessage!); - - let inviterSequence: AccountSequence | null = null; - if (command.inviterReferralCode) { - const referralCode = ReferralCode.create(command.inviterReferralCode); - const referralValidation = await this.validatorService.validateReferralCode(referralCode); - if (!referralValidation.isValid) throw new ApplicationError(referralValidation.errorMessage!); - const inviter = await this.userRepository.findByReferralCode(referralCode); - inviterSequence = inviter!.accountSequence; - } - - const accountSequence = await this.sequenceGenerator.generateNext(); - - const account = UserAccount.createAutomatic({ - accountSequence, - initialDeviceId: command.deviceId, - deviceName: command.deviceName, - inviterSequence, - province: ProvinceCode.create(command.provinceCode || 'DEFAULT'), - city: CityCode.create(command.cityCode || 'DEFAULT'), - }); - - const { mnemonic, wallets } = this.walletGenerator.generateWalletSystem({ - userId: account.userId, - deviceId: command.deviceId, - }); - - account.bindMultipleWalletAddresses(wallets); - await this.userRepository.save(account); - await this.userRepository.saveWallets(account.userId, Array.from(wallets.values())); - - const tokens = await this.tokenService.generateTokenPair({ - userId: account.userId.value, - accountSequence: account.accountSequence.value, - deviceId: command.deviceId, - }); - - await this.eventPublisher.publishAll(account.domainEvents); - account.clearDomainEvents(); - - return { - userId: account.userId.value, - accountSequence: account.accountSequence.value, - referralCode: account.referralCode.value, - mnemonic: mnemonic.value, - walletAddresses: { - kava: wallets.get(ChainType.KAVA)!.address, - dst: wallets.get(ChainType.DST)!.address, - bsc: wallets.get(ChainType.BSC)!.address, - }, - accessToken: tokens.accessToken, - refreshToken: tokens.refreshToken, - }; - } -} diff --git a/backend/services/identity-service/identity-service/src/application/commands/bind-phone/bind-phone.command.ts b/backend/services/identity-service/identity-service/src/application/commands/bind-phone/bind-phone.command.ts deleted file mode 100644 index 65730ed5..00000000 --- a/backend/services/identity-service/identity-service/src/application/commands/bind-phone/bind-phone.command.ts +++ /dev/null @@ -1,7 +0,0 @@ -export class BindPhoneCommand { - constructor( - public readonly userId: string, - public readonly phoneNumber: string, - public readonly smsCode: string, - ) {} -} diff --git a/backend/services/identity-service/identity-service/src/application/commands/bind-phone/bind-phone.handler.ts b/backend/services/identity-service/identity-service/src/application/commands/bind-phone/bind-phone.handler.ts deleted file mode 100644 index 18305e43..00000000 --- a/backend/services/identity-service/identity-service/src/application/commands/bind-phone/bind-phone.handler.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Injectable, Inject } from '@nestjs/common'; -import { BindPhoneCommand } from './bind-phone.command'; -import { UserAccountRepository, USER_ACCOUNT_REPOSITORY } from '@/domain/repositories/user-account.repository.interface'; -import { UserValidatorService } from '@/domain/services'; -import { UserId, PhoneNumber } from '@/domain/value-objects'; -import { RedisService } from '@/infrastructure/redis/redis.service'; -import { EventPublisherService } from '@/infrastructure/kafka/event-publisher.service'; -import { ApplicationError } from '@/shared/exceptions/domain.exception'; - -@Injectable() -export class BindPhoneHandler { - constructor( - @Inject(USER_ACCOUNT_REPOSITORY) - private readonly userRepository: UserAccountRepository, - private readonly validatorService: UserValidatorService, - private readonly redisService: RedisService, - private readonly eventPublisher: EventPublisherService, - ) {} - - async execute(command: BindPhoneCommand): Promise { - const account = await this.userRepository.findById(UserId.create(command.userId)); - if (!account) throw new ApplicationError('用户不存在'); - - const phoneNumber = PhoneNumber.create(command.phoneNumber); - const cachedCode = await this.redisService.get(`sms:bind:${phoneNumber.value}`); - if (cachedCode !== command.smsCode) throw new ApplicationError('验证码错误或已过期'); - - const validation = await this.validatorService.validatePhoneNumber(phoneNumber); - if (!validation.isValid) throw new ApplicationError(validation.errorMessage!); - - account.bindPhoneNumber(phoneNumber); - await this.userRepository.save(account); - await this.redisService.delete(`sms:bind:${phoneNumber.value}`); - await this.eventPublisher.publishAll(account.domainEvents); - account.clearDomainEvents(); - } -} diff --git a/backend/services/identity-service/identity-service/src/application/commands/index.ts b/backend/services/identity-service/identity-service/src/application/commands/index.ts deleted file mode 100644 index 4c85a5ce..00000000 --- a/backend/services/identity-service/identity-service/src/application/commands/index.ts +++ /dev/null @@ -1,206 +0,0 @@ -// ============ Commands ============ -export class AutoCreateAccountCommand { - constructor( - public readonly deviceId: string, - public readonly deviceName?: string, - public readonly inviterReferralCode?: string, - public readonly provinceCode?: string, - public readonly cityCode?: string, - ) {} -} - -export class RecoverByMnemonicCommand { - constructor( - public readonly accountSequence: number, - public readonly mnemonic: string, - public readonly newDeviceId: string, - public readonly deviceName?: string, - ) {} -} - -export class RecoverByPhoneCommand { - constructor( - public readonly accountSequence: number, - public readonly phoneNumber: string, - public readonly smsCode: string, - public readonly newDeviceId: string, - public readonly deviceName?: string, - ) {} -} - -export class AutoLoginCommand { - constructor( - public readonly refreshToken: string, - public readonly deviceId: string, - ) {} -} - -export class RegisterCommand { - constructor( - public readonly phoneNumber: string, - public readonly smsCode: string, - public readonly deviceId: string, - public readonly provinceCode: string, - public readonly cityCode: string, - public readonly deviceName?: string, - public readonly inviterReferralCode?: string, - ) {} -} - -export class LoginCommand { - constructor( - public readonly phoneNumber: string, - public readonly smsCode: string, - public readonly deviceId: string, - ) {} -} - -export class BindPhoneNumberCommand { - constructor( - public readonly userId: string, - public readonly phoneNumber: string, - public readonly smsCode: string, - ) {} -} - -export class UpdateProfileCommand { - constructor( - public readonly userId: string, - public readonly nickname?: string, - public readonly avatarUrl?: string, - public readonly address?: string, - ) {} -} - -export class BindWalletAddressCommand { - constructor( - public readonly userId: string, - public readonly chainType: string, - public readonly address: string, - ) {} -} - -export class SubmitKYCCommand { - constructor( - public readonly userId: string, - public readonly realName: string, - public readonly idCardNumber: string, - public readonly idCardFrontUrl: string, - public readonly idCardBackUrl: string, - ) {} -} - -export class ReviewKYCCommand { - constructor( - public readonly userId: string, - public readonly approved: boolean, - public readonly reason?: string, - ) {} -} - -export class RemoveDeviceCommand { - constructor( - public readonly userId: string, - public readonly currentDeviceId: string, - public readonly deviceIdToRemove: string, - ) {} -} - -export class SendSmsCodeCommand { - constructor( - public readonly phoneNumber: string, - public readonly type: 'REGISTER' | 'LOGIN' | 'BIND' | 'RECOVER', - ) {} -} - -// ============ Queries ============ -export class GetMyProfileQuery { - constructor(public readonly userId: string) {} -} - -export class GetMyDevicesQuery { - constructor( - public readonly userId: string, - public readonly currentDeviceId: string, - ) {} -} - -export class GetUserByReferralCodeQuery { - constructor(public readonly referralCode: string) {} -} - -// ============ Results ============ -export interface AutoCreateAccountResult { - userId: string; - accountSequence: number; - referralCode: string; - mnemonic: string; - walletAddresses: { kava: string; dst: string; bsc: string }; - accessToken: string; - refreshToken: string; -} - -export interface RecoverAccountResult { - userId: string; - accountSequence: number; - nickname: string; - avatarUrl: string | null; - referralCode: string; - accessToken: string; - refreshToken: string; -} - -export interface AutoLoginResult { - userId: string; - accountSequence: number; - accessToken: string; - refreshToken: string; -} - -export interface RegisterResult { - userId: string; - accountSequence: number; - referralCode: string; - accessToken: string; - refreshToken: string; -} - -export interface LoginResult { - userId: string; - accountSequence: number; - accessToken: string; - refreshToken: string; -} - -export interface UserProfileDTO { - userId: string; - accountSequence: number; - phoneNumber: string | null; - nickname: string; - avatarUrl: string | null; - referralCode: string; - province: string; - city: string; - address: string | null; - walletAddresses: Array<{ chainType: string; address: string }>; - kycStatus: string; - kycInfo: { realName: string; idCardNumber: string } | null; - status: string; - registeredAt: Date; - lastLoginAt: Date | null; -} - -export interface DeviceDTO { - deviceId: string; - deviceName: string; - addedAt: Date; - lastActiveAt: Date; - isCurrent: boolean; -} - -export interface UserBriefDTO { - userId: string; - accountSequence: number; - nickname: string; - avatarUrl: string | null; -} diff --git a/backend/services/identity-service/identity-service/src/application/commands/recover-by-mnemonic/recover-by-mnemonic.command.ts b/backend/services/identity-service/identity-service/src/application/commands/recover-by-mnemonic/recover-by-mnemonic.command.ts deleted file mode 100644 index fb3a66e9..00000000 --- a/backend/services/identity-service/identity-service/src/application/commands/recover-by-mnemonic/recover-by-mnemonic.command.ts +++ /dev/null @@ -1,8 +0,0 @@ -export class RecoverByMnemonicCommand { - constructor( - public readonly accountSequence: number, - public readonly mnemonic: string, - public readonly newDeviceId: string, - public readonly deviceName?: string, - ) {} -} diff --git a/backend/services/identity-service/identity-service/src/application/commands/recover-by-mnemonic/recover-by-mnemonic.handler.ts b/backend/services/identity-service/identity-service/src/application/commands/recover-by-mnemonic/recover-by-mnemonic.handler.ts deleted file mode 100644 index 5246391d..00000000 --- a/backend/services/identity-service/identity-service/src/application/commands/recover-by-mnemonic/recover-by-mnemonic.handler.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { Injectable, Inject } from '@nestjs/common'; -import { RecoverByMnemonicCommand } from './recover-by-mnemonic.command'; -import { UserAccountRepository, USER_ACCOUNT_REPOSITORY } from '@/domain/repositories/user-account.repository.interface'; -import { WalletGeneratorService } from '@/domain/services'; -import { AccountSequence, ChainType, Mnemonic } from '@/domain/value-objects'; -import { TokenService } from '@/application/services/token.service'; -import { EventPublisherService } from '@/infrastructure/kafka/event-publisher.service'; -import { ApplicationError } from '@/shared/exceptions/domain.exception'; -import { RecoverAccountResult } from '../index'; - -@Injectable() -export class RecoverByMnemonicHandler { - constructor( - @Inject(USER_ACCOUNT_REPOSITORY) - private readonly userRepository: UserAccountRepository, - private readonly walletGenerator: WalletGeneratorService, - private readonly tokenService: TokenService, - private readonly eventPublisher: EventPublisherService, - ) {} - - async execute(command: RecoverByMnemonicCommand): Promise { - const accountSequence = AccountSequence.create(command.accountSequence); - const account = await this.userRepository.findByAccountSequence(accountSequence); - if (!account) throw new ApplicationError('账户序列号不存在'); - if (!account.isActive) throw new ApplicationError('账户已冻结或注销'); - - const mnemonic = Mnemonic.create(command.mnemonic); - const wallets = this.walletGenerator.recoverWalletSystem({ - userId: account.userId, - mnemonic, - deviceId: command.newDeviceId, - }); - - const kavaWallet = account.getWalletAddress(ChainType.KAVA); - if (!kavaWallet || kavaWallet.address !== wallets.get(ChainType.KAVA)!.address) { - throw new ApplicationError('助记词错误'); - } - - account.addDevice(command.newDeviceId, command.deviceName); - account.recordLogin(); - await this.userRepository.save(account); - - const tokens = await this.tokenService.generateTokenPair({ - userId: account.userId.value, - accountSequence: account.accountSequence.value, - deviceId: command.newDeviceId, - }); - - await this.eventPublisher.publishAll(account.domainEvents); - account.clearDomainEvents(); - - return { - userId: account.userId.value, - accountSequence: account.accountSequence.value, - nickname: account.nickname, - avatarUrl: account.avatarUrl, - referralCode: account.referralCode.value, - accessToken: tokens.accessToken, - refreshToken: tokens.refreshToken, - }; - } -} diff --git a/backend/services/identity-service/identity-service/src/application/commands/recover-by-phone/recover-by-phone.command.ts b/backend/services/identity-service/identity-service/src/application/commands/recover-by-phone/recover-by-phone.command.ts deleted file mode 100644 index ae19629c..00000000 --- a/backend/services/identity-service/identity-service/src/application/commands/recover-by-phone/recover-by-phone.command.ts +++ /dev/null @@ -1,9 +0,0 @@ -export class RecoverByPhoneCommand { - constructor( - public readonly accountSequence: number, - public readonly phoneNumber: string, - public readonly smsCode: string, - public readonly newDeviceId: string, - public readonly deviceName?: string, - ) {} -} diff --git a/backend/services/identity-service/identity-service/src/application/commands/recover-by-phone/recover-by-phone.handler.ts b/backend/services/identity-service/identity-service/src/application/commands/recover-by-phone/recover-by-phone.handler.ts deleted file mode 100644 index cebd6d23..00000000 --- a/backend/services/identity-service/identity-service/src/application/commands/recover-by-phone/recover-by-phone.handler.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Injectable, Inject } from '@nestjs/common'; -import { RecoverByPhoneCommand } from './recover-by-phone.command'; -import { UserAccountRepository, USER_ACCOUNT_REPOSITORY } from '@/domain/repositories/user-account.repository.interface'; -import { AccountSequence, PhoneNumber } from '@/domain/value-objects'; -import { TokenService } from '@/application/services/token.service'; -import { RedisService } from '@/infrastructure/redis/redis.service'; -import { EventPublisherService } from '@/infrastructure/kafka/event-publisher.service'; -import { ApplicationError } from '@/shared/exceptions/domain.exception'; -import { RecoverAccountResult } from '../index'; - -@Injectable() -export class RecoverByPhoneHandler { - constructor( - @Inject(USER_ACCOUNT_REPOSITORY) - private readonly userRepository: UserAccountRepository, - private readonly tokenService: TokenService, - private readonly redisService: RedisService, - private readonly eventPublisher: EventPublisherService, - ) {} - - async execute(command: RecoverByPhoneCommand): Promise { - const accountSequence = AccountSequence.create(command.accountSequence); - const account = await this.userRepository.findByAccountSequence(accountSequence); - if (!account) throw new ApplicationError('账户序列号不存在'); - if (!account.isActive) throw new ApplicationError('账户已冻结或注销'); - if (!account.phoneNumber) throw new ApplicationError('该账户未绑定手机号,请使用助记词恢复'); - - const phoneNumber = PhoneNumber.create(command.phoneNumber); - if (!account.phoneNumber.equals(phoneNumber)) throw new ApplicationError('手机号与账户不匹配'); - - const cachedCode = await this.redisService.get(`sms:recover:${phoneNumber.value}`); - if (cachedCode !== command.smsCode) throw new ApplicationError('验证码错误或已过期'); - - account.addDevice(command.newDeviceId, command.deviceName); - account.recordLogin(); - await this.userRepository.save(account); - await this.redisService.delete(`sms:recover:${phoneNumber.value}`); - - const tokens = await this.tokenService.generateTokenPair({ - userId: account.userId.value, - accountSequence: account.accountSequence.value, - deviceId: command.newDeviceId, - }); - - await this.eventPublisher.publishAll(account.domainEvents); - account.clearDomainEvents(); - - return { - userId: account.userId.value, - accountSequence: account.accountSequence.value, - nickname: account.nickname, - avatarUrl: account.avatarUrl, - referralCode: account.referralCode.value, - accessToken: tokens.accessToken, - refreshToken: tokens.refreshToken, - }; - } -} diff --git a/backend/services/identity-service/identity-service/src/application/queries/get-my-devices/get-my-devices.handler.ts b/backend/services/identity-service/identity-service/src/application/queries/get-my-devices/get-my-devices.handler.ts deleted file mode 100644 index 6628acd3..00000000 --- a/backend/services/identity-service/identity-service/src/application/queries/get-my-devices/get-my-devices.handler.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Injectable, Inject } from '@nestjs/common'; -import { GetMyDevicesQuery } from './get-my-devices.query'; -import { UserAccountRepository, USER_ACCOUNT_REPOSITORY } from '@/domain/repositories/user-account.repository.interface'; -import { UserId } from '@/domain/value-objects'; -import { ApplicationError } from '@/shared/exceptions/domain.exception'; -import { DeviceDTO } from '@/application/commands'; - -@Injectable() -export class GetMyDevicesHandler { - constructor( - @Inject(USER_ACCOUNT_REPOSITORY) - private readonly userRepository: UserAccountRepository, - ) {} - - async execute(query: GetMyDevicesQuery): Promise { - const account = await this.userRepository.findById(UserId.create(query.userId)); - if (!account) throw new ApplicationError('用户不存在'); - - return account.getAllDevices().map((device) => ({ - deviceId: device.deviceId, - deviceName: device.deviceName, - addedAt: device.addedAt, - lastActiveAt: device.lastActiveAt, - isCurrent: device.deviceId === query.currentDeviceId, - })); - } -} diff --git a/backend/services/identity-service/identity-service/src/application/queries/get-my-devices/get-my-devices.query.ts b/backend/services/identity-service/identity-service/src/application/queries/get-my-devices/get-my-devices.query.ts deleted file mode 100644 index e68fbf50..00000000 --- a/backend/services/identity-service/identity-service/src/application/queries/get-my-devices/get-my-devices.query.ts +++ /dev/null @@ -1,6 +0,0 @@ -export class GetMyDevicesQuery { - constructor( - public readonly userId: string, - public readonly currentDeviceId: string, - ) {} -} diff --git a/backend/services/identity-service/identity-service/src/application/queries/get-my-profile/get-my-profile.handler.ts b/backend/services/identity-service/identity-service/src/application/queries/get-my-profile/get-my-profile.handler.ts deleted file mode 100644 index 8d5d67c1..00000000 --- a/backend/services/identity-service/identity-service/src/application/queries/get-my-profile/get-my-profile.handler.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Injectable, Inject } from '@nestjs/common'; -import { GetMyProfileQuery } from './get-my-profile.query'; -import { UserAccountRepository, USER_ACCOUNT_REPOSITORY } from '@/domain/repositories/user-account.repository.interface'; -import { UserAccount } from '@/domain/aggregates/user-account/user-account.aggregate'; -import { UserId } from '@/domain/value-objects'; -import { ApplicationError } from '@/shared/exceptions/domain.exception'; -import { UserProfileDTO } from '@/application/commands'; - -@Injectable() -export class GetMyProfileHandler { - constructor( - @Inject(USER_ACCOUNT_REPOSITORY) - private readonly userRepository: UserAccountRepository, - ) {} - - async execute(query: GetMyProfileQuery): Promise { - const account = await this.userRepository.findById(UserId.create(query.userId)); - if (!account) throw new ApplicationError('用户不存在'); - return this.toDTO(account); - } - - private toDTO(account: UserAccount): UserProfileDTO { - return { - userId: account.userId.value, - accountSequence: account.accountSequence.value, - phoneNumber: account.phoneNumber?.masked() || null, - nickname: account.nickname, - avatarUrl: account.avatarUrl, - referralCode: account.referralCode.value, - province: account.province.value, - city: account.city.value, - address: account.addressDetail, - walletAddresses: account.getAllWalletAddresses().map((wa) => ({ - chainType: wa.chainType, - address: wa.address, - })), - kycStatus: account.kycStatus, - kycInfo: account.kycInfo - ? { realName: account.kycInfo.realName, idCardNumber: account.kycInfo.maskedIdCardNumber() } - : null, - status: account.status, - registeredAt: account.registeredAt, - lastLoginAt: account.lastLoginAt, - }; - } -} diff --git a/backend/services/identity-service/identity-service/src/application/queries/get-my-profile/get-my-profile.query.ts b/backend/services/identity-service/identity-service/src/application/queries/get-my-profile/get-my-profile.query.ts deleted file mode 100644 index c0f7806e..00000000 --- a/backend/services/identity-service/identity-service/src/application/queries/get-my-profile/get-my-profile.query.ts +++ /dev/null @@ -1,3 +0,0 @@ -export class GetMyProfileQuery { - constructor(public readonly userId: string) {} -} diff --git a/backend/services/identity-service/identity-service/src/application/services/token.service.ts b/backend/services/identity-service/identity-service/src/application/services/token.service.ts deleted file mode 100644 index 207b9b3a..00000000 --- a/backend/services/identity-service/identity-service/src/application/services/token.service.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { JwtService } from '@nestjs/jwt'; -import { ConfigService } from '@nestjs/config'; -import { createHash } from 'crypto'; -import { PrismaService } from '@/infrastructure/persistence/prisma/prisma.service'; -import { ApplicationError } from '@/shared/exceptions/domain.exception'; - -export interface TokenPayload { - userId: string; - accountSequence: number; - deviceId: string; - type: 'access' | 'refresh'; -} - -@Injectable() -export class TokenService { - constructor( - private readonly jwtService: JwtService, - private readonly configService: ConfigService, - private readonly prisma: PrismaService, - ) {} - - async generateTokenPair(payload: { - userId: string; - accountSequence: number; - deviceId: string; - }): Promise<{ accessToken: string; refreshToken: string }> { - const accessToken = this.jwtService.sign( - { ...payload, type: 'access' }, - { expiresIn: this.configService.get('JWT_ACCESS_EXPIRES_IN', '2h') }, - ); - - const refreshToken = this.jwtService.sign( - { ...payload, type: 'refresh' }, - { expiresIn: this.configService.get('JWT_REFRESH_EXPIRES_IN', '30d') }, - ); - - // Save refresh token hash - const tokenHash = this.hashToken(refreshToken); - await this.prisma.deviceToken.create({ - data: { - userId: BigInt(payload.userId), - deviceId: payload.deviceId, - refreshTokenHash: tokenHash, - expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), - }, - }); - - return { accessToken, refreshToken }; - } - - async verifyRefreshToken(token: string): Promise<{ - userId: string; - accountSequence: number; - deviceId: string; - }> { - try { - const payload = this.jwtService.verify(token); - if (payload.type !== 'refresh') { - throw new ApplicationError('无效的RefreshToken'); - } - - const tokenHash = this.hashToken(token); - const storedToken = await this.prisma.deviceToken.findUnique({ - where: { refreshTokenHash: tokenHash }, - }); - - if (!storedToken || storedToken.revokedAt) { - throw new ApplicationError('RefreshToken已失效'); - } - - return { - userId: payload.userId, - accountSequence: payload.accountSequence, - deviceId: payload.deviceId, - }; - } catch (error) { - if (error instanceof ApplicationError) throw error; - throw new ApplicationError('RefreshToken已过期或无效'); - } - } - - async revokeDeviceTokens(userId: string, deviceId: string): Promise { - await this.prisma.deviceToken.updateMany({ - where: { userId: BigInt(userId), deviceId, revokedAt: null }, - data: { revokedAt: new Date() }, - }); - } - - private hashToken(token: string): string { - return createHash('sha256').update(token).digest('hex'); - } -} diff --git a/backend/services/identity-service/identity-service/src/application/services/user-application.service.ts b/backend/services/identity-service/identity-service/src/application/services/user-application.service.ts deleted file mode 100644 index d38bbba7..00000000 --- a/backend/services/identity-service/identity-service/src/application/services/user-application.service.ts +++ /dev/null @@ -1,424 +0,0 @@ -import { Injectable, Inject } from '@nestjs/common'; -import { UserAccountRepository, USER_ACCOUNT_REPOSITORY } from '@/domain/repositories/user-account.repository.interface'; -import { UserAccount } from '@/domain/aggregates/user-account/user-account.aggregate'; -import { - AccountSequenceGeneratorService, UserValidatorService, WalletGeneratorService, -} from '@/domain/services'; -import { - UserId, PhoneNumber, ReferralCode, AccountSequence, ProvinceCode, CityCode, - ChainType, Mnemonic, KYCInfo, -} from '@/domain/value-objects'; -import { TokenService } from './token.service'; -import { RedisService } from '@/infrastructure/redis/redis.service'; -import { SmsService } from '@/infrastructure/external/sms/sms.service'; -import { EventPublisherService } from '@/infrastructure/kafka/event-publisher.service'; -import { ApplicationError } from '@/shared/exceptions/domain.exception'; -import { - AutoCreateAccountCommand, RecoverByMnemonicCommand, RecoverByPhoneCommand, - AutoLoginCommand, RegisterCommand, LoginCommand, BindPhoneNumberCommand, - UpdateProfileCommand, SubmitKYCCommand, ReviewKYCCommand, RemoveDeviceCommand, - SendSmsCodeCommand, GetMyProfileQuery, GetMyDevicesQuery, GetUserByReferralCodeQuery, - AutoCreateAccountResult, RecoverAccountResult, AutoLoginResult, RegisterResult, - LoginResult, UserProfileDTO, DeviceDTO, UserBriefDTO, -} from '../commands'; - -@Injectable() -export class UserApplicationService { - constructor( - @Inject(USER_ACCOUNT_REPOSITORY) - private readonly userRepository: UserAccountRepository, - private readonly sequenceGenerator: AccountSequenceGeneratorService, - private readonly validatorService: UserValidatorService, - private readonly walletGenerator: WalletGeneratorService, - private readonly tokenService: TokenService, - private readonly redisService: RedisService, - private readonly smsService: SmsService, - private readonly eventPublisher: EventPublisherService, - ) {} - - async autoCreateAccount(command: AutoCreateAccountCommand): Promise { - const deviceValidation = await this.validatorService.validateDeviceId(command.deviceId); - if (!deviceValidation.isValid) throw new ApplicationError(deviceValidation.errorMessage!); - - let inviterSequence: AccountSequence | null = null; - if (command.inviterReferralCode) { - const referralCode = ReferralCode.create(command.inviterReferralCode); - const referralValidation = await this.validatorService.validateReferralCode(referralCode); - if (!referralValidation.isValid) throw new ApplicationError(referralValidation.errorMessage!); - const inviter = await this.userRepository.findByReferralCode(referralCode); - inviterSequence = inviter!.accountSequence; - } - - const accountSequence = await this.sequenceGenerator.generateNext(); - - const account = UserAccount.createAutomatic({ - accountSequence, - initialDeviceId: command.deviceId, - deviceName: command.deviceName, - inviterSequence, - province: ProvinceCode.create(command.provinceCode || 'DEFAULT'), - city: CityCode.create(command.cityCode || 'DEFAULT'), - }); - - const { mnemonic, wallets } = this.walletGenerator.generateWalletSystem({ - userId: account.userId, - deviceId: command.deviceId, - }); - - account.bindMultipleWalletAddresses(wallets); - await this.userRepository.save(account); - await this.userRepository.saveWallets(account.userId, Array.from(wallets.values())); - - const tokens = await this.tokenService.generateTokenPair({ - userId: account.userId.value, - accountSequence: account.accountSequence.value, - deviceId: command.deviceId, - }); - - await this.eventPublisher.publishAll(account.domainEvents); - account.clearDomainEvents(); - - return { - userId: account.userId.value, - accountSequence: account.accountSequence.value, - referralCode: account.referralCode.value, - mnemonic: mnemonic.value, - walletAddresses: { - kava: wallets.get(ChainType.KAVA)!.address, - dst: wallets.get(ChainType.DST)!.address, - bsc: wallets.get(ChainType.BSC)!.address, - }, - accessToken: tokens.accessToken, - refreshToken: tokens.refreshToken, - }; - } - - async recoverByMnemonic(command: RecoverByMnemonicCommand): Promise { - const accountSequence = AccountSequence.create(command.accountSequence); - const account = await this.userRepository.findByAccountSequence(accountSequence); - if (!account) throw new ApplicationError('账户序列号不存在'); - if (!account.isActive) throw new ApplicationError('账户已冻结或注销'); - - const mnemonic = Mnemonic.create(command.mnemonic); - const wallets = this.walletGenerator.recoverWalletSystem({ - userId: account.userId, - mnemonic, - deviceId: command.newDeviceId, - }); - - const kavaWallet = account.getWalletAddress(ChainType.KAVA); - if (!kavaWallet || kavaWallet.address !== wallets.get(ChainType.KAVA)!.address) { - throw new ApplicationError('助记词错误'); - } - - account.addDevice(command.newDeviceId, command.deviceName); - account.recordLogin(); - await this.userRepository.save(account); - - const tokens = await this.tokenService.generateTokenPair({ - userId: account.userId.value, - accountSequence: account.accountSequence.value, - deviceId: command.newDeviceId, - }); - - await this.eventPublisher.publishAll(account.domainEvents); - account.clearDomainEvents(); - - return { - userId: account.userId.value, - accountSequence: account.accountSequence.value, - nickname: account.nickname, - avatarUrl: account.avatarUrl, - referralCode: account.referralCode.value, - accessToken: tokens.accessToken, - refreshToken: tokens.refreshToken, - }; - } - - async recoverByPhone(command: RecoverByPhoneCommand): Promise { - const accountSequence = AccountSequence.create(command.accountSequence); - const account = await this.userRepository.findByAccountSequence(accountSequence); - if (!account) throw new ApplicationError('账户序列号不存在'); - if (!account.isActive) throw new ApplicationError('账户已冻结或注销'); - if (!account.phoneNumber) throw new ApplicationError('该账户未绑定手机号,请使用助记词恢复'); - - const phoneNumber = PhoneNumber.create(command.phoneNumber); - if (!account.phoneNumber.equals(phoneNumber)) throw new ApplicationError('手机号与账户不匹配'); - - const cachedCode = await this.redisService.get(`sms:recover:${phoneNumber.value}`); - if (cachedCode !== command.smsCode) throw new ApplicationError('验证码错误或已过期'); - - account.addDevice(command.newDeviceId, command.deviceName); - account.recordLogin(); - await this.userRepository.save(account); - await this.redisService.delete(`sms:recover:${phoneNumber.value}`); - - const tokens = await this.tokenService.generateTokenPair({ - userId: account.userId.value, - accountSequence: account.accountSequence.value, - deviceId: command.newDeviceId, - }); - - await this.eventPublisher.publishAll(account.domainEvents); - account.clearDomainEvents(); - - return { - userId: account.userId.value, - accountSequence: account.accountSequence.value, - nickname: account.nickname, - avatarUrl: account.avatarUrl, - referralCode: account.referralCode.value, - accessToken: tokens.accessToken, - refreshToken: tokens.refreshToken, - }; - } - - async autoLogin(command: AutoLoginCommand): Promise { - const payload = await this.tokenService.verifyRefreshToken(command.refreshToken); - const account = await this.userRepository.findById(UserId.create(payload.userId)); - if (!account || !account.isActive) throw new ApplicationError('账户不存在或已冻结'); - if (!account.isDeviceAuthorized(command.deviceId)) { - throw new ApplicationError('设备未授权,请重新登录', 'DEVICE_UNAUTHORIZED'); - } - - account.addDevice(command.deviceId); - account.recordLogin(); - await this.userRepository.save(account); - - const tokens = await this.tokenService.generateTokenPair({ - userId: account.userId.value, - accountSequence: account.accountSequence.value, - deviceId: command.deviceId, - }); - - return { - userId: account.userId.value, - accountSequence: account.accountSequence.value, - accessToken: tokens.accessToken, - refreshToken: tokens.refreshToken, - }; - } - - async sendSmsCode(command: SendSmsCodeCommand): Promise { - const phoneNumber = PhoneNumber.create(command.phoneNumber); - const code = this.generateSmsCode(); - const cacheKey = `sms:${command.type.toLowerCase()}:${phoneNumber.value}`; - - await this.smsService.sendVerificationCode(phoneNumber.value, code); - await this.redisService.set(cacheKey, code, 300); - } - - async register(command: RegisterCommand): Promise { - const phoneNumber = PhoneNumber.create(command.phoneNumber); - const cachedCode = await this.redisService.get(`sms:register:${phoneNumber.value}`); - if (cachedCode !== command.smsCode) throw new ApplicationError('验证码错误或已过期'); - - const phoneValidation = await this.validatorService.validatePhoneNumber(phoneNumber); - if (!phoneValidation.isValid) throw new ApplicationError(phoneValidation.errorMessage!); - - let inviterSequence: AccountSequence | null = null; - if (command.inviterReferralCode) { - const referralCode = ReferralCode.create(command.inviterReferralCode); - const referralValidation = await this.validatorService.validateReferralCode(referralCode); - if (!referralValidation.isValid) throw new ApplicationError(referralValidation.errorMessage!); - const inviter = await this.userRepository.findByReferralCode(referralCode); - inviterSequence = inviter!.accountSequence; - } - - const accountSequence = await this.sequenceGenerator.generateNext(); - - const account = UserAccount.create({ - accountSequence, - phoneNumber, - initialDeviceId: command.deviceId, - deviceName: command.deviceName, - inviterSequence, - province: ProvinceCode.create(command.provinceCode), - city: CityCode.create(command.cityCode), - }); - - await this.userRepository.save(account); - await this.redisService.delete(`sms:register:${phoneNumber.value}`); - await this.eventPublisher.publishAll(account.domainEvents); - account.clearDomainEvents(); - - const tokens = await this.tokenService.generateTokenPair({ - userId: account.userId.value, - accountSequence: account.accountSequence.value, - deviceId: command.deviceId, - }); - - return { - userId: account.userId.value, - accountSequence: account.accountSequence.value, - referralCode: account.referralCode.value, - accessToken: tokens.accessToken, - refreshToken: tokens.refreshToken, - }; - } - - async login(command: LoginCommand): Promise { - const phoneNumber = PhoneNumber.create(command.phoneNumber); - const cachedCode = await this.redisService.get(`sms:login:${phoneNumber.value}`); - if (cachedCode !== command.smsCode) throw new ApplicationError('验证码错误或已过期'); - - const account = await this.userRepository.findByPhoneNumber(phoneNumber); - if (!account) throw new ApplicationError('用户不存在'); - if (!account.isActive) throw new ApplicationError('账户已冻结或注销'); - - account.addDevice(command.deviceId); - account.recordLogin(); - await this.userRepository.save(account); - await this.redisService.delete(`sms:login:${phoneNumber.value}`); - - const tokens = await this.tokenService.generateTokenPair({ - userId: account.userId.value, - accountSequence: account.accountSequence.value, - deviceId: command.deviceId, - }); - - return { - userId: account.userId.value, - accountSequence: account.accountSequence.value, - accessToken: tokens.accessToken, - refreshToken: tokens.refreshToken, - }; - } - - async bindPhoneNumber(command: BindPhoneNumberCommand): Promise { - const account = await this.userRepository.findById(UserId.create(command.userId)); - if (!account) throw new ApplicationError('用户不存在'); - - const phoneNumber = PhoneNumber.create(command.phoneNumber); - const cachedCode = await this.redisService.get(`sms:bind:${phoneNumber.value}`); - if (cachedCode !== command.smsCode) throw new ApplicationError('验证码错误或已过期'); - - const validation = await this.validatorService.validatePhoneNumber(phoneNumber); - if (!validation.isValid) throw new ApplicationError(validation.errorMessage!); - - account.bindPhoneNumber(phoneNumber); - await this.userRepository.save(account); - await this.redisService.delete(`sms:bind:${phoneNumber.value}`); - await this.eventPublisher.publishAll(account.domainEvents); - account.clearDomainEvents(); - } - - async updateProfile(command: UpdateProfileCommand): Promise { - const account = await this.userRepository.findById(UserId.create(command.userId)); - if (!account) throw new ApplicationError('用户不存在'); - - account.updateProfile({ - nickname: command.nickname, - avatarUrl: command.avatarUrl, - address: command.address, - }); - - await this.userRepository.save(account); - } - - async submitKYC(command: SubmitKYCCommand): Promise { - const account = await this.userRepository.findById(UserId.create(command.userId)); - if (!account) throw new ApplicationError('用户不存在'); - - const kycInfo = KYCInfo.create({ - realName: command.realName, - idCardNumber: command.idCardNumber, - idCardFrontUrl: command.idCardFrontUrl, - idCardBackUrl: command.idCardBackUrl, - }); - - account.submitKYC(kycInfo); - await this.userRepository.save(account); - await this.eventPublisher.publishAll(account.domainEvents); - account.clearDomainEvents(); - } - - async reviewKYC(command: ReviewKYCCommand): Promise { - const account = await this.userRepository.findById(UserId.create(command.userId)); - if (!account) throw new ApplicationError('用户不存在'); - - if (command.approved) { - account.approveKYC(); - } else { - account.rejectKYC(command.reason || '审核未通过'); - } - - await this.userRepository.save(account); - await this.eventPublisher.publishAll(account.domainEvents); - account.clearDomainEvents(); - } - - async getMyDevices(query: GetMyDevicesQuery): Promise { - const account = await this.userRepository.findById(UserId.create(query.userId)); - if (!account) throw new ApplicationError('用户不存在'); - - return account.getAllDevices().map((device) => ({ - deviceId: device.deviceId, - deviceName: device.deviceName, - addedAt: device.addedAt, - lastActiveAt: device.lastActiveAt, - isCurrent: device.deviceId === query.currentDeviceId, - })); - } - - async removeDevice(command: RemoveDeviceCommand): Promise { - const account = await this.userRepository.findById(UserId.create(command.userId)); - if (!account) throw new ApplicationError('用户不存在'); - if (command.deviceIdToRemove === command.currentDeviceId) { - throw new ApplicationError('不能删除当前设备'); - } - - account.removeDevice(command.deviceIdToRemove); - await this.userRepository.save(account); - await this.tokenService.revokeDeviceTokens(account.userId.value, command.deviceIdToRemove); - await this.eventPublisher.publishAll(account.domainEvents); - account.clearDomainEvents(); - } - - async getMyProfile(query: GetMyProfileQuery): Promise { - const account = await this.userRepository.findById(UserId.create(query.userId)); - if (!account) throw new ApplicationError('用户不存在'); - return this.toUserProfileDTO(account); - } - - async getUserByReferralCode(query: GetUserByReferralCodeQuery): Promise { - const account = await this.userRepository.findByReferralCode(ReferralCode.create(query.referralCode)); - if (!account) return null; - - return { - userId: account.userId.value, - accountSequence: account.accountSequence.value, - nickname: account.nickname, - avatarUrl: account.avatarUrl, - }; - } - - private toUserProfileDTO(account: UserAccount): UserProfileDTO { - return { - userId: account.userId.value, - accountSequence: account.accountSequence.value, - phoneNumber: account.phoneNumber?.masked() || null, - nickname: account.nickname, - avatarUrl: account.avatarUrl, - referralCode: account.referralCode.value, - province: account.province.value, - city: account.city.value, - address: account.addressDetail, - walletAddresses: account.getAllWalletAddresses().map((wa) => ({ - chainType: wa.chainType, - address: wa.address, - })), - kycStatus: account.kycStatus, - kycInfo: account.kycInfo - ? { realName: account.kycInfo.realName, idCardNumber: account.kycInfo.maskedIdCardNumber() } - : null, - status: account.status, - registeredAt: account.registeredAt, - lastLoginAt: account.lastLoginAt, - }; - } - - private generateSmsCode(): string { - return String(Math.floor(100000 + Math.random() * 900000)); - } -} diff --git a/backend/services/identity-service/identity-service/src/config/app.config.ts b/backend/services/identity-service/identity-service/src/config/app.config.ts deleted file mode 100644 index 0a6f3545..00000000 --- a/backend/services/identity-service/identity-service/src/config/app.config.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const appConfig = () => ({ - port: parseInt(process.env.APP_PORT || '3000', 10), - env: process.env.APP_ENV || 'development', -}); diff --git a/backend/services/identity-service/identity-service/src/config/database.config.ts b/backend/services/identity-service/identity-service/src/config/database.config.ts deleted file mode 100644 index 3cc9c86d..00000000 --- a/backend/services/identity-service/identity-service/src/config/database.config.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const databaseConfig = () => ({ - url: process.env.DATABASE_URL, -}); diff --git a/backend/services/identity-service/identity-service/src/config/index.ts b/backend/services/identity-service/identity-service/src/config/index.ts deleted file mode 100644 index d9c0f6ae..00000000 --- a/backend/services/identity-service/identity-service/src/config/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -export const appConfig = () => ({ - port: parseInt(process.env.APP_PORT || '3000', 10), - env: process.env.APP_ENV || 'development', -}); - -export const databaseConfig = () => ({ - url: process.env.DATABASE_URL, -}); - -export const jwtConfig = () => ({ - secret: process.env.JWT_SECRET || 'default-secret', - accessExpiresIn: process.env.JWT_ACCESS_EXPIRES_IN || '2h', - refreshExpiresIn: process.env.JWT_REFRESH_EXPIRES_IN || '30d', -}); - -export const redisConfig = () => ({ - host: process.env.REDIS_HOST || 'localhost', - port: parseInt(process.env.REDIS_PORT || '6379', 10), - password: process.env.REDIS_PASSWORD || undefined, - db: parseInt(process.env.REDIS_DB || '0', 10), -}); - -export const kafkaConfig = () => ({ - brokers: (process.env.KAFKA_BROKERS || 'localhost:9092').split(','), - clientId: process.env.KAFKA_CLIENT_ID || 'identity-service', - groupId: process.env.KAFKA_GROUP_ID || 'identity-service-group', -}); - -export const smsConfig = () => ({ - apiUrl: process.env.SMS_API_URL || '', - apiKey: process.env.SMS_API_KEY || '', -}); - -export const walletConfig = () => ({ - encryptionSalt: process.env.WALLET_ENCRYPTION_SALT || 'rwa-wallet-salt', -}); diff --git a/backend/services/identity-service/identity-service/src/config/jwt.config.ts b/backend/services/identity-service/identity-service/src/config/jwt.config.ts deleted file mode 100644 index cddfa983..00000000 --- a/backend/services/identity-service/identity-service/src/config/jwt.config.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const jwtConfig = () => ({ - secret: process.env.JWT_SECRET || 'default-secret', - accessExpiresIn: process.env.JWT_ACCESS_EXPIRES_IN || '2h', - refreshExpiresIn: process.env.JWT_REFRESH_EXPIRES_IN || '30d', -}); diff --git a/backend/services/identity-service/identity-service/src/config/kafka.config.ts b/backend/services/identity-service/identity-service/src/config/kafka.config.ts deleted file mode 100644 index 5a32f93c..00000000 --- a/backend/services/identity-service/identity-service/src/config/kafka.config.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const kafkaConfig = () => ({ - brokers: (process.env.KAFKA_BROKERS || 'localhost:9092').split(','), - clientId: process.env.KAFKA_CLIENT_ID || 'identity-service', - groupId: process.env.KAFKA_GROUP_ID || 'identity-service-group', -}); diff --git a/backend/services/identity-service/identity-service/src/config/redis.config.ts b/backend/services/identity-service/identity-service/src/config/redis.config.ts deleted file mode 100644 index 6178285c..00000000 --- a/backend/services/identity-service/identity-service/src/config/redis.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const redisConfig = () => ({ - host: process.env.REDIS_HOST || 'localhost', - port: parseInt(process.env.REDIS_PORT || '6379', 10), - password: process.env.REDIS_PASSWORD || undefined, - db: parseInt(process.env.REDIS_DB || '0', 10), -}); diff --git a/backend/services/identity-service/identity-service/src/domain/aggregates/user-account/user-account.aggregate.ts b/backend/services/identity-service/identity-service/src/domain/aggregates/user-account/user-account.aggregate.ts deleted file mode 100644 index 6da2fa17..00000000 --- a/backend/services/identity-service/identity-service/src/domain/aggregates/user-account/user-account.aggregate.ts +++ /dev/null @@ -1,347 +0,0 @@ -import { DomainError } from '@/shared/exceptions/domain.exception'; -import { - UserId, AccountSequence, PhoneNumber, ReferralCode, ProvinceCode, CityCode, - DeviceInfo, ChainType, KYCInfo, KYCStatus, AccountStatus, -} from '@/domain/value-objects'; -import { WalletAddress } from '@/domain/entities/wallet-address.entity'; -import { - DomainEvent, UserAccountAutoCreatedEvent, UserAccountCreatedEvent, - DeviceAddedEvent, DeviceRemovedEvent, PhoneNumberBoundEvent, - WalletAddressBoundEvent, MultipleWalletAddressesBoundEvent, - KYCSubmittedEvent, KYCVerifiedEvent, KYCRejectedEvent, - UserLocationUpdatedEvent, UserAccountFrozenEvent, UserAccountDeactivatedEvent, -} from '@/domain/events'; - -export class UserAccount { - private readonly _userId: UserId; - private readonly _accountSequence: AccountSequence; - private _devices: Map; - private _phoneNumber: PhoneNumber | null; - private _nickname: string; - private _avatarUrl: string | null; - private readonly _inviterSequence: AccountSequence | null; - private readonly _referralCode: ReferralCode; - private _province: ProvinceCode; - private _city: CityCode; - private _address: string | null; - private _walletAddresses: Map; - private _kycInfo: KYCInfo | null; - private _kycStatus: KYCStatus; - private _status: AccountStatus; - private readonly _registeredAt: Date; - private _lastLoginAt: Date | null; - private _updatedAt: Date; - private _domainEvents: DomainEvent[] = []; - - // Getters - get userId(): UserId { return this._userId; } - get accountSequence(): AccountSequence { return this._accountSequence; } - get phoneNumber(): PhoneNumber | null { return this._phoneNumber; } - get nickname(): string { return this._nickname; } - get avatarUrl(): string | null { return this._avatarUrl; } - get inviterSequence(): AccountSequence | null { return this._inviterSequence; } - get referralCode(): ReferralCode { return this._referralCode; } - get province(): ProvinceCode { return this._province; } - get city(): CityCode { return this._city; } - get addressDetail(): string | null { return this._address; } - get kycInfo(): KYCInfo | null { return this._kycInfo; } - get kycStatus(): KYCStatus { return this._kycStatus; } - get status(): AccountStatus { return this._status; } - get registeredAt(): Date { return this._registeredAt; } - get lastLoginAt(): Date | null { return this._lastLoginAt; } - get updatedAt(): Date { return this._updatedAt; } - get isActive(): boolean { return this._status === AccountStatus.ACTIVE; } - get isKYCVerified(): boolean { return this._kycStatus === KYCStatus.VERIFIED; } - get domainEvents(): DomainEvent[] { return [...this._domainEvents]; } - - private constructor( - userId: UserId, accountSequence: AccountSequence, devices: Map, - phoneNumber: PhoneNumber | null, nickname: string, avatarUrl: string | null, - inviterSequence: AccountSequence | null, referralCode: ReferralCode, - province: ProvinceCode, city: CityCode, address: string | null, - walletAddresses: Map, kycInfo: KYCInfo | null, - kycStatus: KYCStatus, status: AccountStatus, registeredAt: Date, - lastLoginAt: Date | null, updatedAt: Date, - ) { - this._userId = userId; - this._accountSequence = accountSequence; - this._devices = devices; - this._phoneNumber = phoneNumber; - this._nickname = nickname; - this._avatarUrl = avatarUrl; - this._inviterSequence = inviterSequence; - this._referralCode = referralCode; - this._province = province; - this._city = city; - this._address = address; - this._walletAddresses = walletAddresses; - this._kycInfo = kycInfo; - this._kycStatus = kycStatus; - this._status = status; - this._registeredAt = registeredAt; - this._lastLoginAt = lastLoginAt; - this._updatedAt = updatedAt; - } - - static createAutomatic(params: { - accountSequence: AccountSequence; - initialDeviceId: string; - deviceName?: string; - inviterSequence: AccountSequence | null; - province: ProvinceCode; - city: CityCode; - }): UserAccount { - const devices = new Map(); - devices.set(params.initialDeviceId, new DeviceInfo( - params.initialDeviceId, params.deviceName || '未命名设备', new Date(), new Date(), - )); - - const account = new UserAccount( - UserId.generate(), params.accountSequence, devices, null, - `用户${params.accountSequence.value}`, null, params.inviterSequence, - ReferralCode.generate(), params.province, params.city, null, - new Map(), null, KYCStatus.NOT_VERIFIED, AccountStatus.ACTIVE, - new Date(), null, new Date(), - ); - - account.addDomainEvent(new UserAccountAutoCreatedEvent({ - userId: account.userId.value, - accountSequence: params.accountSequence.value, - initialDeviceId: params.initialDeviceId, - inviterSequence: params.inviterSequence?.value || null, - province: params.province.value, - city: params.city.value, - registeredAt: account._registeredAt, - })); - - return account; - } - - static create(params: { - accountSequence: AccountSequence; - phoneNumber: PhoneNumber; - initialDeviceId: string; - deviceName?: string; - inviterSequence: AccountSequence | null; - province: ProvinceCode; - city: CityCode; - }): UserAccount { - const devices = new Map(); - devices.set(params.initialDeviceId, new DeviceInfo( - params.initialDeviceId, params.deviceName || '未命名设备', new Date(), new Date(), - )); - - const account = new UserAccount( - UserId.generate(), params.accountSequence, devices, params.phoneNumber, - `用户${params.accountSequence.value}`, null, params.inviterSequence, - ReferralCode.generate(), params.province, params.city, null, - new Map(), null, KYCStatus.NOT_VERIFIED, AccountStatus.ACTIVE, - new Date(), null, new Date(), - ); - - account.addDomainEvent(new UserAccountCreatedEvent({ - userId: account.userId.value, - accountSequence: params.accountSequence.value, - phoneNumber: params.phoneNumber.value, - initialDeviceId: params.initialDeviceId, - inviterSequence: params.inviterSequence?.value || null, - province: params.province.value, - city: params.city.value, - registeredAt: account._registeredAt, - })); - - return account; - } - - static reconstruct(params: { - userId: string; accountSequence: number; devices: DeviceInfo[]; - phoneNumber: string | null; nickname: string; avatarUrl: string | null; - inviterSequence: number | null; referralCode: string; - province: string; city: string; address: string | null; - walletAddresses: WalletAddress[]; kycInfo: KYCInfo | null; - kycStatus: KYCStatus; status: AccountStatus; - registeredAt: Date; lastLoginAt: Date | null; updatedAt: Date; - }): UserAccount { - const deviceMap = new Map(); - params.devices.forEach(d => deviceMap.set(d.deviceId, d)); - - const walletMap = new Map(); - params.walletAddresses.forEach(w => walletMap.set(w.chainType, w)); - - return new UserAccount( - UserId.create(params.userId), - AccountSequence.create(params.accountSequence), - deviceMap, - params.phoneNumber ? PhoneNumber.create(params.phoneNumber) : null, - params.nickname, - params.avatarUrl, - params.inviterSequence ? AccountSequence.create(params.inviterSequence) : null, - ReferralCode.create(params.referralCode), - ProvinceCode.create(params.province), - CityCode.create(params.city), - params.address, - walletMap, - params.kycInfo, - params.kycStatus, - params.status, - params.registeredAt, - params.lastLoginAt, - params.updatedAt, - ); - } - - addDevice(deviceId: string, deviceName?: string): void { - this.ensureActive(); - if (this._devices.size >= 5 && !this._devices.has(deviceId)) { - throw new DomainError('最多允许5个设备同时登录'); - } - if (this._devices.has(deviceId)) { - this._devices.get(deviceId)!.updateActivity(); - } else { - this._devices.set(deviceId, new DeviceInfo(deviceId, deviceName || '未命名设备', new Date(), new Date())); - this.addDomainEvent(new DeviceAddedEvent({ - userId: this.userId.value, - accountSequence: this.accountSequence.value, - deviceId, - deviceName: deviceName || '未命名设备', - })); - } - this._updatedAt = new Date(); - } - - removeDevice(deviceId: string): void { - this.ensureActive(); - if (!this._devices.has(deviceId)) throw new DomainError('设备不存在'); - if (this._devices.size <= 1) throw new DomainError('至少保留一个设备'); - this._devices.delete(deviceId); - this._updatedAt = new Date(); - this.addDomainEvent(new DeviceRemovedEvent({ userId: this.userId.value, deviceId })); - } - - isDeviceAuthorized(deviceId: string): boolean { - return this._devices.has(deviceId); - } - - getAllDevices(): DeviceInfo[] { - return Array.from(this._devices.values()); - } - - updateProfile(params: { nickname?: string; avatarUrl?: string; address?: string }): void { - this.ensureActive(); - if (params.nickname) this._nickname = params.nickname; - if (params.avatarUrl !== undefined) this._avatarUrl = params.avatarUrl; - if (params.address !== undefined) this._address = params.address; - this._updatedAt = new Date(); - } - - updateLocation(province: ProvinceCode, city: CityCode): void { - this.ensureActive(); - this._province = province; - this._city = city; - this._updatedAt = new Date(); - this.addDomainEvent(new UserLocationUpdatedEvent({ - userId: this.userId.value, province: province.value, city: city.value, - })); - } - - bindPhoneNumber(phoneNumber: PhoneNumber): void { - this.ensureActive(); - if (this._phoneNumber) throw new DomainError('已绑定手机号,不可重复绑定'); - this._phoneNumber = phoneNumber; - this._updatedAt = new Date(); - this.addDomainEvent(new PhoneNumberBoundEvent({ userId: this.userId.value, phoneNumber: phoneNumber.value })); - } - - bindWalletAddress(chainType: ChainType, address: string): void { - this.ensureActive(); - if (this._walletAddresses.has(chainType)) throw new DomainError(`已绑定${chainType}地址`); - const walletAddress = WalletAddress.create({ userId: this.userId, chainType, address }); - this._walletAddresses.set(chainType, walletAddress); - this._updatedAt = new Date(); - this.addDomainEvent(new WalletAddressBoundEvent({ userId: this.userId.value, chainType, address })); - } - - bindMultipleWalletAddresses(wallets: Map): void { - this.ensureActive(); - for (const [chainType, wallet] of wallets) { - if (this._walletAddresses.has(chainType)) throw new DomainError(`已绑定${chainType}地址`); - this._walletAddresses.set(chainType, wallet); - } - this._updatedAt = new Date(); - this.addDomainEvent(new MultipleWalletAddressesBoundEvent({ - userId: this.userId.value, - addresses: Array.from(wallets.entries()).map(([chainType, wallet]) => ({ chainType, address: wallet.address })), - })); - } - - submitKYC(kycInfo: KYCInfo): void { - this.ensureActive(); - if (this._kycStatus === KYCStatus.VERIFIED) throw new DomainError('已通过KYC认证,不可重复提交'); - this._kycInfo = kycInfo; - this._kycStatus = KYCStatus.PENDING; - this._updatedAt = new Date(); - this.addDomainEvent(new KYCSubmittedEvent({ - userId: this.userId.value, realName: kycInfo.realName, idCardNumber: kycInfo.idCardNumber, - })); - } - - approveKYC(): void { - if (this._kycStatus !== KYCStatus.PENDING) throw new DomainError('只有待审核状态才能通过KYC'); - this._kycStatus = KYCStatus.VERIFIED; - this._updatedAt = new Date(); - this.addDomainEvent(new KYCVerifiedEvent({ userId: this.userId.value, verifiedAt: new Date() })); - } - - rejectKYC(reason: string): void { - if (this._kycStatus !== KYCStatus.PENDING) throw new DomainError('只有待审核状态才能拒绝KYC'); - this._kycStatus = KYCStatus.REJECTED; - this._updatedAt = new Date(); - this.addDomainEvent(new KYCRejectedEvent({ userId: this.userId.value, reason })); - } - - recordLogin(): void { - this.ensureActive(); - this._lastLoginAt = new Date(); - this._updatedAt = new Date(); - } - - freeze(reason: string): void { - if (this._status === AccountStatus.FROZEN) throw new DomainError('账户已冻结'); - this._status = AccountStatus.FROZEN; - this._updatedAt = new Date(); - this.addDomainEvent(new UserAccountFrozenEvent({ userId: this.userId.value, reason })); - } - - unfreeze(): void { - if (this._status !== AccountStatus.FROZEN) throw new DomainError('账户未冻结'); - this._status = AccountStatus.ACTIVE; - this._updatedAt = new Date(); - } - - deactivate(): void { - if (this._status === AccountStatus.DEACTIVATED) throw new DomainError('账户已注销'); - this._status = AccountStatus.DEACTIVATED; - this._updatedAt = new Date(); - this.addDomainEvent(new UserAccountDeactivatedEvent({ userId: this.userId.value, deactivatedAt: new Date() })); - } - - getWalletAddress(chainType: ChainType): WalletAddress | null { - return this._walletAddresses.get(chainType) || null; - } - - getAllWalletAddresses(): WalletAddress[] { - return Array.from(this._walletAddresses.values()); - } - - private ensureActive(): void { - if (this._status !== AccountStatus.ACTIVE) throw new DomainError('账户已冻结或注销'); - } - - private addDomainEvent(event: DomainEvent): void { - this._domainEvents.push(event); - } - - clearDomainEvents(): void { - this._domainEvents = []; - } -} diff --git a/backend/services/identity-service/identity-service/src/domain/aggregates/user-account/user-account.factory.ts b/backend/services/identity-service/identity-service/src/domain/aggregates/user-account/user-account.factory.ts deleted file mode 100644 index b911b407..00000000 --- a/backend/services/identity-service/identity-service/src/domain/aggregates/user-account/user-account.factory.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { UserAccount } from './user-account.aggregate'; -import { AccountSequence, PhoneNumber, ProvinceCode, CityCode } from '@/domain/value-objects'; - -@Injectable() -export class UserAccountFactory { - createAutomatic(params: { - accountSequence: AccountSequence; - initialDeviceId: string; - deviceName?: string; - inviterSequence: AccountSequence | null; - province: ProvinceCode; - city: CityCode; - }): UserAccount { - return UserAccount.createAutomatic(params); - } - - create(params: { - accountSequence: AccountSequence; - phoneNumber: PhoneNumber; - initialDeviceId: string; - deviceName?: string; - inviterSequence: AccountSequence | null; - province: ProvinceCode; - city: CityCode; - }): UserAccount { - return UserAccount.create(params); - } -} diff --git a/backend/services/identity-service/identity-service/src/domain/aggregates/user-account/user-account.spec.ts b/backend/services/identity-service/identity-service/src/domain/aggregates/user-account/user-account.spec.ts deleted file mode 100644 index 53f739fd..00000000 --- a/backend/services/identity-service/identity-service/src/domain/aggregates/user-account/user-account.spec.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { UserAccount } from './user-account.aggregate'; -import { AccountSequence, ProvinceCode, CityCode } from '@/domain/value-objects'; -import { DomainError } from '@/shared/exceptions/domain.exception'; - -describe('UserAccount', () => { - const createTestAccount = () => { - return UserAccount.createAutomatic({ - accountSequence: AccountSequence.create(1), - initialDeviceId: 'device-001', - deviceName: 'Test Device', - inviterSequence: null, - province: ProvinceCode.create('110000'), - city: CityCode.create('110100'), - }); - }; - - describe('createAutomatic', () => { - it('should create account with default values', () => { - const account = createTestAccount(); - expect(account.accountSequence.value).toBe(1); - expect(account.nickname).toBe('用户1'); - expect(account.isActive).toBe(true); - expect(account.phoneNumber).toBeNull(); - }); - - it('should add initial device', () => { - const account = createTestAccount(); - expect(account.isDeviceAuthorized('device-001')).toBe(true); - expect(account.getAllDevices()).toHaveLength(1); - }); - }); - - describe('addDevice', () => { - it('should add new device', () => { - const account = createTestAccount(); - account.addDevice('device-002', 'New Device'); - expect(account.getAllDevices()).toHaveLength(2); - }); - - it('should throw error when exceeding device limit', () => { - const account = createTestAccount(); - account.addDevice('device-002'); - account.addDevice('device-003'); - account.addDevice('device-004'); - account.addDevice('device-005'); - - expect(() => account.addDevice('device-006')).toThrow(DomainError); - }); - }); - - describe('removeDevice', () => { - it('should remove existing device', () => { - const account = createTestAccount(); - account.addDevice('device-002'); - account.removeDevice('device-002'); - expect(account.getAllDevices()).toHaveLength(1); - }); - - it('should not remove last device', () => { - const account = createTestAccount(); - expect(() => account.removeDevice('device-001')).toThrow(DomainError); - }); - }); - - describe('freeze/unfreeze', () => { - it('should freeze active account', () => { - const account = createTestAccount(); - account.freeze('Test reason'); - expect(account.isActive).toBe(false); - }); - - it('should unfreeze frozen account', () => { - const account = createTestAccount(); - account.freeze('Test reason'); - account.unfreeze(); - expect(account.isActive).toBe(true); - }); - }); -}); diff --git a/backend/services/identity-service/identity-service/src/domain/domain.module.ts b/backend/services/identity-service/identity-service/src/domain/domain.module.ts deleted file mode 100644 index fcf40c9b..00000000 --- a/backend/services/identity-service/identity-service/src/domain/domain.module.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Module } from '@nestjs/common'; -import { AccountSequenceGeneratorService, UserValidatorService, WalletGeneratorService } from './services'; -import { UserAccountFactory } from './aggregates/user-account/user-account.factory'; -import { USER_ACCOUNT_REPOSITORY } from './repositories/user-account.repository.interface'; -import { UserAccountRepositoryImpl } from '@/infrastructure/persistence/repositories/user-account.repository.impl'; -import { InfrastructureModule } from '@/infrastructure/infrastructure.module'; - -@Module({ - imports: [InfrastructureModule], - providers: [ - { provide: USER_ACCOUNT_REPOSITORY, useClass: UserAccountRepositoryImpl }, - AccountSequenceGeneratorService, - UserValidatorService, - WalletGeneratorService, - UserAccountFactory, - ], - exports: [ - USER_ACCOUNT_REPOSITORY, - AccountSequenceGeneratorService, - UserValidatorService, - WalletGeneratorService, - UserAccountFactory, - ], -}) -export class DomainModule {} diff --git a/backend/services/identity-service/identity-service/src/domain/entities/wallet-address.entity.ts b/backend/services/identity-service/identity-service/src/domain/entities/wallet-address.entity.ts deleted file mode 100644 index 225498f8..00000000 --- a/backend/services/identity-service/identity-service/src/domain/entities/wallet-address.entity.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { HDKey } from '@scure/bip32'; -import { createHash } from 'crypto'; -import { bech32 } from 'bech32'; -import { Wallet } from 'ethers'; -import { DomainError } from '@/shared/exceptions/domain.exception'; -import { - AddressId, - UserId, - ChainType, - CHAIN_CONFIG, - AddressStatus, - Mnemonic, - MnemonicEncryption, -} from '@/domain/value-objects'; - -export class WalletAddress { - private readonly _addressId: AddressId; - private readonly _userId: UserId; - private readonly _chainType: ChainType; - private readonly _address: string; - private readonly _encryptedMnemonic: string; - private _status: AddressStatus; - private readonly _boundAt: Date; - - get addressId(): AddressId { return this._addressId; } - get userId(): UserId { return this._userId; } - get chainType(): ChainType { return this._chainType; } - get address(): string { return this._address; } - get encryptedMnemonic(): string { return this._encryptedMnemonic; } - get status(): AddressStatus { return this._status; } - get boundAt(): Date { return this._boundAt; } - - private constructor( - addressId: AddressId, - userId: UserId, - chainType: ChainType, - address: string, - encryptedMnemonic: string, - status: AddressStatus, - boundAt: Date, - ) { - this._addressId = addressId; - this._userId = userId; - this._chainType = chainType; - this._address = address; - this._encryptedMnemonic = encryptedMnemonic; - this._status = status; - this._boundAt = boundAt; - } - - static create(params: { userId: UserId; chainType: ChainType; address: string }): WalletAddress { - if (!this.validateAddress(params.chainType, params.address)) { - throw new DomainError(`${params.chainType}地址格式错误`); - } - return new WalletAddress( - AddressId.generate(), - params.userId, - params.chainType, - params.address, - '', - AddressStatus.ACTIVE, - new Date(), - ); - } - - static createFromMnemonic(params: { - userId: UserId; - chainType: ChainType; - mnemonic: Mnemonic; - encryptionKey: string; - }): WalletAddress { - const address = this.deriveAddress(params.chainType, params.mnemonic); - const encryptedMnemonic = MnemonicEncryption.encrypt(params.mnemonic.value, params.encryptionKey); - return new WalletAddress( - AddressId.generate(), - params.userId, - params.chainType, - address, - encryptedMnemonic, - AddressStatus.ACTIVE, - new Date(), - ); - } - - static reconstruct(params: { - addressId: string; - userId: string; - chainType: ChainType; - address: string; - encryptedMnemonic: string; - status: AddressStatus; - boundAt: Date; - }): WalletAddress { - return new WalletAddress( - AddressId.create(params.addressId), - UserId.create(params.userId), - params.chainType, - params.address, - params.encryptedMnemonic, - params.status, - params.boundAt, - ); - } - - disable(): void { - this._status = AddressStatus.DISABLED; - } - - enable(): void { - this._status = AddressStatus.ACTIVE; - } - - decryptMnemonic(encryptionKey: string): Mnemonic { - if (!this._encryptedMnemonic) { - throw new DomainError('该地址没有加密助记词'); - } - const mnemonicStr = MnemonicEncryption.decrypt(this._encryptedMnemonic, encryptionKey); - return Mnemonic.create(mnemonicStr); - } - - private static deriveAddress(chainType: ChainType, mnemonic: Mnemonic): string { - const seed = mnemonic.toSeed(); - const config = CHAIN_CONFIG[chainType]; - - switch (chainType) { - case ChainType.KAVA: - case ChainType.DST: - return this.deriveCosmosAddress(Buffer.from(seed), config.derivationPath, config.prefix); - case ChainType.BSC: - return this.deriveEVMAddress(Buffer.from(seed), config.derivationPath); - default: - throw new DomainError(`不支持的链类型: ${chainType}`); - } - } - - private static deriveCosmosAddress(seed: Buffer, path: string, prefix: string): string { - const hdkey = HDKey.fromMasterSeed(seed); - const childKey = hdkey.derive(path); - if (!childKey.publicKey) throw new DomainError('无法派生公钥'); - - const hash = createHash('sha256').update(childKey.publicKey).digest(); - const addressHash = createHash('ripemd160').update(hash).digest(); - const words = bech32.toWords(addressHash); - return bech32.encode(prefix, words); - } - - private static deriveEVMAddress(seed: Buffer, path: string): string { - const hdkey = HDKey.fromMasterSeed(seed); - const childKey = hdkey.derive(path); - if (!childKey.privateKey) throw new DomainError('无法派生私钥'); - - const wallet = new Wallet(Buffer.from(childKey.privateKey).toString('hex')); - return wallet.address; - } - - private static validateAddress(chainType: ChainType, address: string): boolean { - switch (chainType) { - case ChainType.KAVA: - case ChainType.DST: - return /^(kava|dst)1[a-z0-9]{38}$/.test(address); - case ChainType.BSC: - return /^0x[a-fA-F0-9]{40}$/.test(address); - default: - return false; - } - } -} diff --git a/backend/services/identity-service/identity-service/src/domain/events/device-added.event.ts b/backend/services/identity-service/identity-service/src/domain/events/device-added.event.ts deleted file mode 100644 index 62dc40f3..00000000 --- a/backend/services/identity-service/identity-service/src/domain/events/device-added.event.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { DomainEvent } from './index'; - -export class DeviceAddedEvent extends DomainEvent { - constructor( - public readonly payload: { - userId: string; - accountSequence: number; - deviceId: string; - deviceName: string; - }, - ) { - super(); - } - - get eventType(): string { - return 'DeviceAdded'; - } -} diff --git a/backend/services/identity-service/identity-service/src/domain/events/index.ts b/backend/services/identity-service/identity-service/src/domain/events/index.ts deleted file mode 100644 index 8234990e..00000000 --- a/backend/services/identity-service/identity-service/src/domain/events/index.ts +++ /dev/null @@ -1,174 +0,0 @@ -export abstract class DomainEvent { - public readonly occurredAt: Date; - public readonly eventId: string; - - constructor() { - this.occurredAt = new Date(); - this.eventId = crypto.randomUUID(); - } - - abstract get eventType(): string; -} - -export class UserAccountAutoCreatedEvent extends DomainEvent { - constructor( - public readonly payload: { - userId: string; - accountSequence: number; - initialDeviceId: string; - inviterSequence: number | null; - province: string; - city: string; - registeredAt: Date; - }, - ) { - super(); - } - - get eventType(): string { - return 'UserAccountAutoCreated'; - } -} - -export class UserAccountCreatedEvent extends DomainEvent { - constructor( - public readonly payload: { - userId: string; - accountSequence: number; - phoneNumber: string; - initialDeviceId: string; - inviterSequence: number | null; - province: string; - city: string; - registeredAt: Date; - }, - ) { - super(); - } - - get eventType(): string { - return 'UserAccountCreated'; - } -} - -export class DeviceAddedEvent extends DomainEvent { - constructor( - public readonly payload: { - userId: string; - accountSequence: number; - deviceId: string; - deviceName: string; - }, - ) { - super(); - } - - get eventType(): string { - return 'DeviceAdded'; - } -} - -export class DeviceRemovedEvent extends DomainEvent { - constructor(public readonly payload: { userId: string; deviceId: string }) { - super(); - } - - get eventType(): string { - return 'DeviceRemoved'; - } -} - -export class PhoneNumberBoundEvent extends DomainEvent { - constructor(public readonly payload: { userId: string; phoneNumber: string }) { - super(); - } - - get eventType(): string { - return 'PhoneNumberBound'; - } -} - -export class WalletAddressBoundEvent extends DomainEvent { - constructor(public readonly payload: { userId: string; chainType: string; address: string }) { - super(); - } - - get eventType(): string { - return 'WalletAddressBound'; - } -} - -export class MultipleWalletAddressesBoundEvent extends DomainEvent { - constructor( - public readonly payload: { - userId: string; - addresses: Array<{ chainType: string; address: string }>; - }, - ) { - super(); - } - - get eventType(): string { - return 'MultipleWalletAddressesBound'; - } -} - -export class KYCSubmittedEvent extends DomainEvent { - constructor(public readonly payload: { userId: string; realName: string; idCardNumber: string }) { - super(); - } - - get eventType(): string { - return 'KYCSubmitted'; - } -} - -export class KYCVerifiedEvent extends DomainEvent { - constructor(public readonly payload: { userId: string; verifiedAt: Date }) { - super(); - } - - get eventType(): string { - return 'KYCVerified'; - } -} - -export class KYCRejectedEvent extends DomainEvent { - constructor(public readonly payload: { userId: string; reason: string }) { - super(); - } - - get eventType(): string { - return 'KYCRejected'; - } -} - -export class UserLocationUpdatedEvent extends DomainEvent { - constructor(public readonly payload: { userId: string; province: string; city: string }) { - super(); - } - - get eventType(): string { - return 'UserLocationUpdated'; - } -} - -export class UserAccountFrozenEvent extends DomainEvent { - constructor(public readonly payload: { userId: string; reason: string }) { - super(); - } - - get eventType(): string { - return 'UserAccountFrozen'; - } -} - -export class UserAccountDeactivatedEvent extends DomainEvent { - constructor(public readonly payload: { userId: string; deactivatedAt: Date }) { - super(); - } - - get eventType(): string { - return 'UserAccountDeactivated'; - } -} diff --git a/backend/services/identity-service/identity-service/src/domain/events/phone-bound.event.ts b/backend/services/identity-service/identity-service/src/domain/events/phone-bound.event.ts deleted file mode 100644 index 78aa8a19..00000000 --- a/backend/services/identity-service/identity-service/src/domain/events/phone-bound.event.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { DomainEvent } from './index'; - -export class PhoneNumberBoundEvent extends DomainEvent { - constructor(public readonly payload: { userId: string; phoneNumber: string }) { - super(); - } - - get eventType(): string { - return 'PhoneNumberBound'; - } -} diff --git a/backend/services/identity-service/identity-service/src/domain/events/user-account-created.event.ts b/backend/services/identity-service/identity-service/src/domain/events/user-account-created.event.ts deleted file mode 100644 index dcd1b390..00000000 --- a/backend/services/identity-service/identity-service/src/domain/events/user-account-created.event.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { DomainEvent } from './index'; - -export class UserAccountCreatedEvent extends DomainEvent { - constructor( - public readonly payload: { - userId: string; - accountSequence: number; - phoneNumber: string; - initialDeviceId: string; - inviterSequence: number | null; - province: string; - city: string; - registeredAt: Date; - }, - ) { - super(); - } - - get eventType(): string { - return 'UserAccountCreated'; - } -} diff --git a/backend/services/identity-service/identity-service/src/domain/repositories/user-account.repository.interface.ts b/backend/services/identity-service/identity-service/src/domain/repositories/user-account.repository.interface.ts deleted file mode 100644 index 3584bb49..00000000 --- a/backend/services/identity-service/identity-service/src/domain/repositories/user-account.repository.interface.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { UserAccount } from '@/domain/aggregates/user-account/user-account.aggregate'; -import { WalletAddress } from '@/domain/entities/wallet-address.entity'; -import { - UserId, AccountSequence, PhoneNumber, ReferralCode, ChainType, AccountStatus, KYCStatus, -} from '@/domain/value-objects'; - -export interface Pagination { - page: number; - limit: number; -} - -export interface UserAccountRepository { - save(account: UserAccount): Promise; - saveWallets(userId: UserId, wallets: WalletAddress[]): Promise; - findById(userId: UserId): Promise; - findByAccountSequence(sequence: AccountSequence): Promise; - findByDeviceId(deviceId: string): Promise; - findByPhoneNumber(phoneNumber: PhoneNumber): Promise; - findByReferralCode(referralCode: ReferralCode): Promise; - findByWalletAddress(chainType: ChainType, address: string): Promise; - getMaxAccountSequence(): Promise; - getNextAccountSequence(): Promise; - findUsers( - filters?: { status?: AccountStatus; kycStatus?: KYCStatus; province?: string; city?: string; keyword?: string }, - pagination?: Pagination, - ): Promise; - countUsers(filters?: { status?: AccountStatus; kycStatus?: KYCStatus }): Promise; -} - -export const USER_ACCOUNT_REPOSITORY = Symbol('USER_ACCOUNT_REPOSITORY'); diff --git a/backend/services/identity-service/identity-service/src/domain/services/account-sequence-generator.service.ts b/backend/services/identity-service/identity-service/src/domain/services/account-sequence-generator.service.ts deleted file mode 100644 index 875d3e3e..00000000 --- a/backend/services/identity-service/identity-service/src/domain/services/account-sequence-generator.service.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Injectable, Inject } from '@nestjs/common'; -import { UserAccountRepository, USER_ACCOUNT_REPOSITORY } from '@/domain/repositories/user-account.repository.interface'; -import { AccountSequence } from '@/domain/value-objects'; - -@Injectable() -export class AccountSequenceGeneratorService { - constructor( - @Inject(USER_ACCOUNT_REPOSITORY) - private readonly repository: UserAccountRepository, - ) {} - - async generateNext(): Promise { - return this.repository.getNextAccountSequence(); - } -} diff --git a/backend/services/identity-service/identity-service/src/domain/services/index.ts b/backend/services/identity-service/identity-service/src/domain/services/index.ts deleted file mode 100644 index 52dc56e3..00000000 --- a/backend/services/identity-service/identity-service/src/domain/services/index.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { Injectable, Inject } from '@nestjs/common'; -import { createHash } from 'crypto'; -import { UserAccount } from '@/domain/aggregates/user-account/user-account.aggregate'; -import { WalletAddress } from '@/domain/entities/wallet-address.entity'; -import { UserAccountRepository, USER_ACCOUNT_REPOSITORY } from '@/domain/repositories/user-account.repository.interface'; -import { - AccountSequence, PhoneNumber, ReferralCode, ChainType, Mnemonic, UserId, -} from '@/domain/value-objects'; - -// ============ ValidationResult ============ -export class ValidationResult { - private constructor( - public readonly isValid: boolean, - public readonly errorMessage: string | null, - ) {} - - static success(): ValidationResult { - return new ValidationResult(true, null); - } - - static failure(message: string): ValidationResult { - return new ValidationResult(false, message); - } -} - -// ============ AccountSequenceGeneratorService ============ -@Injectable() -export class AccountSequenceGeneratorService { - constructor( - @Inject(USER_ACCOUNT_REPOSITORY) - private readonly repository: UserAccountRepository, - ) {} - - async generateNext(): Promise { - return this.repository.getNextAccountSequence(); - } -} - -// ============ UserValidatorService ============ -@Injectable() -export class UserValidatorService { - constructor( - @Inject(USER_ACCOUNT_REPOSITORY) - private readonly repository: UserAccountRepository, - ) {} - - async validatePhoneNumber(phoneNumber: PhoneNumber): Promise { - const existing = await this.repository.findByPhoneNumber(phoneNumber); - if (existing) return ValidationResult.failure('该手机号已注册'); - return ValidationResult.success(); - } - - async validateDeviceId(deviceId: string): Promise { - const existing = await this.repository.findByDeviceId(deviceId); - if (existing) return ValidationResult.failure('该设备已创建账户'); - return ValidationResult.success(); - } - - async validateReferralCode(referralCode: ReferralCode): Promise { - const inviter = await this.repository.findByReferralCode(referralCode); - if (!inviter) return ValidationResult.failure('推荐码不存在'); - if (!inviter.isActive) return ValidationResult.failure('推荐人账户已冻结或注销'); - return ValidationResult.success(); - } - - async validateWalletAddress(chainType: ChainType, address: string): Promise { - const existing = await this.repository.findByWalletAddress(chainType, address); - if (existing) return ValidationResult.failure('该地址已被其他账户绑定'); - return ValidationResult.success(); - } -} - -// ============ WalletGeneratorService ============ -@Injectable() -export class WalletGeneratorService { - generateWalletSystem(params: { userId: UserId; deviceId: string }): { - mnemonic: Mnemonic; - wallets: Map; - } { - const mnemonic = Mnemonic.generate(); - const encryptionKey = this.deriveEncryptionKey(params.deviceId, params.userId.value); - - const wallets = new Map(); - const chains = [ChainType.KAVA, ChainType.DST, ChainType.BSC]; - - for (const chainType of chains) { - const wallet = WalletAddress.createFromMnemonic({ - userId: params.userId, - chainType, - mnemonic, - encryptionKey, - }); - wallets.set(chainType, wallet); - } - - return { mnemonic, wallets }; - } - - recoverWalletSystem(params: { userId: UserId; mnemonic: Mnemonic; deviceId: string }): Map { - const encryptionKey = this.deriveEncryptionKey(params.deviceId, params.userId.value); - const wallets = new Map(); - const chains = [ChainType.KAVA, ChainType.DST, ChainType.BSC]; - - for (const chainType of chains) { - const wallet = WalletAddress.createFromMnemonic({ - userId: params.userId, - chainType, - mnemonic: params.mnemonic, - encryptionKey, - }); - wallets.set(chainType, wallet); - } - - return wallets; - } - - private deriveEncryptionKey(deviceId: string, userId: string): string { - const input = `${deviceId}:${userId}`; - return createHash('sha256').update(input).digest('hex'); - } -} diff --git a/backend/services/identity-service/identity-service/src/domain/services/user-validator.service.ts b/backend/services/identity-service/identity-service/src/domain/services/user-validator.service.ts deleted file mode 100644 index cc6f2242..00000000 --- a/backend/services/identity-service/identity-service/src/domain/services/user-validator.service.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Injectable, Inject } from '@nestjs/common'; -import { UserAccountRepository, USER_ACCOUNT_REPOSITORY } from '@/domain/repositories/user-account.repository.interface'; -import { PhoneNumber, ReferralCode, ChainType } from '@/domain/value-objects'; - -export class ValidationResult { - private constructor( - public readonly isValid: boolean, - public readonly errorMessage: string | null, - ) {} - - static success(): ValidationResult { - return new ValidationResult(true, null); - } - - static failure(message: string): ValidationResult { - return new ValidationResult(false, message); - } -} - -@Injectable() -export class UserValidatorService { - constructor( - @Inject(USER_ACCOUNT_REPOSITORY) - private readonly repository: UserAccountRepository, - ) {} - - async validatePhoneNumber(phoneNumber: PhoneNumber): Promise { - const existing = await this.repository.findByPhoneNumber(phoneNumber); - if (existing) return ValidationResult.failure('该手机号已注册'); - return ValidationResult.success(); - } - - async validateDeviceId(deviceId: string): Promise { - const existing = await this.repository.findByDeviceId(deviceId); - if (existing) return ValidationResult.failure('该设备已创建账户'); - return ValidationResult.success(); - } - - async validateReferralCode(referralCode: ReferralCode): Promise { - const inviter = await this.repository.findByReferralCode(referralCode); - if (!inviter) return ValidationResult.failure('推荐码不存在'); - if (!inviter.isActive) return ValidationResult.failure('推荐人账户已冻结或注销'); - return ValidationResult.success(); - } - - async validateWalletAddress(chainType: ChainType, address: string): Promise { - const existing = await this.repository.findByWalletAddress(chainType, address); - if (existing) return ValidationResult.failure('该地址已被其他账户绑定'); - return ValidationResult.success(); - } -} diff --git a/backend/services/identity-service/identity-service/src/domain/services/wallet-generator.service.ts b/backend/services/identity-service/identity-service/src/domain/services/wallet-generator.service.ts deleted file mode 100644 index 5ee9af64..00000000 --- a/backend/services/identity-service/identity-service/src/domain/services/wallet-generator.service.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { createHash } from 'crypto'; -import { WalletAddress } from '@/domain/entities/wallet-address.entity'; -import { ChainType, Mnemonic, UserId } from '@/domain/value-objects'; - -@Injectable() -export class WalletGeneratorService { - generateWalletSystem(params: { userId: UserId; deviceId: string }): { - mnemonic: Mnemonic; - wallets: Map; - } { - const mnemonic = Mnemonic.generate(); - const encryptionKey = this.deriveEncryptionKey(params.deviceId, params.userId.value); - - const wallets = new Map(); - const chains = [ChainType.KAVA, ChainType.DST, ChainType.BSC]; - - for (const chainType of chains) { - const wallet = WalletAddress.createFromMnemonic({ - userId: params.userId, - chainType, - mnemonic, - encryptionKey, - }); - wallets.set(chainType, wallet); - } - - return { mnemonic, wallets }; - } - - recoverWalletSystem(params: { userId: UserId; mnemonic: Mnemonic; deviceId: string }): Map { - const encryptionKey = this.deriveEncryptionKey(params.deviceId, params.userId.value); - const wallets = new Map(); - const chains = [ChainType.KAVA, ChainType.DST, ChainType.BSC]; - - for (const chainType of chains) { - const wallet = WalletAddress.createFromMnemonic({ - userId: params.userId, - chainType, - mnemonic: params.mnemonic, - encryptionKey, - }); - wallets.set(chainType, wallet); - } - - return wallets; - } - - private deriveEncryptionKey(deviceId: string, userId: string): string { - const input = `${deviceId}:${userId}`; - return createHash('sha256').update(input).digest('hex'); - } -} diff --git a/backend/services/identity-service/identity-service/src/domain/value-objects/account-sequence.vo.ts b/backend/services/identity-service/identity-service/src/domain/value-objects/account-sequence.vo.ts deleted file mode 100644 index f12f6d35..00000000 --- a/backend/services/identity-service/identity-service/src/domain/value-objects/account-sequence.vo.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { DomainError } from '@/shared/exceptions/domain.exception'; - -export class AccountSequence { - constructor(public readonly value: number) { - if (value <= 0) throw new DomainError('账户序列号必须大于0'); - } - - static create(value: number): AccountSequence { - return new AccountSequence(value); - } - - static next(current: AccountSequence): AccountSequence { - return new AccountSequence(current.value + 1); - } - - equals(other: AccountSequence): boolean { - return this.value === other.value; - } -} diff --git a/backend/services/identity-service/identity-service/src/domain/value-objects/device-info.vo.ts b/backend/services/identity-service/identity-service/src/domain/value-objects/device-info.vo.ts deleted file mode 100644 index f1b571b8..00000000 --- a/backend/services/identity-service/identity-service/src/domain/value-objects/device-info.vo.ts +++ /dev/null @@ -1,20 +0,0 @@ -export class DeviceInfo { - private _lastActiveAt: Date; - - constructor( - public readonly deviceId: string, - public readonly deviceName: string, - public readonly addedAt: Date, - lastActiveAt: Date, - ) { - this._lastActiveAt = lastActiveAt; - } - - get lastActiveAt(): Date { - return this._lastActiveAt; - } - - updateActivity(): void { - this._lastActiveAt = new Date(); - } -} diff --git a/backend/services/identity-service/identity-service/src/domain/value-objects/index.ts b/backend/services/identity-service/identity-service/src/domain/value-objects/index.ts deleted file mode 100644 index a3800b80..00000000 --- a/backend/services/identity-service/identity-service/src/domain/value-objects/index.ts +++ /dev/null @@ -1,262 +0,0 @@ -import { DomainError } from '@/shared/exceptions/domain.exception'; -import { createHash, createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'crypto'; -import * as bip39 from '@scure/bip39'; -import { wordlist } from '@scure/bip39/wordlists/english'; - -// ============ UserId ============ -export class UserId { - constructor(public readonly value: string) { - if (!value) throw new DomainError('UserId不能为空'); - } - - static generate(): UserId { - return new UserId(crypto.randomUUID()); - } - - static create(value: string): UserId { - return new UserId(value); - } - - equals(other: UserId): boolean { - return this.value === other.value; - } -} - -// ============ AccountSequence ============ -export class AccountSequence { - constructor(public readonly value: number) { - if (value <= 0) throw new DomainError('账户序列号必须大于0'); - } - - static create(value: number): AccountSequence { - return new AccountSequence(value); - } - - static next(current: AccountSequence): AccountSequence { - return new AccountSequence(current.value + 1); - } - - equals(other: AccountSequence): boolean { - return this.value === other.value; - } -} - -// ============ PhoneNumber ============ -export class PhoneNumber { - constructor(public readonly value: string) { - if (!/^1[3-9]\d{9}$/.test(value)) { - throw new DomainError('手机号格式错误'); - } - } - - static create(value: string): PhoneNumber { - return new PhoneNumber(value); - } - - equals(other: PhoneNumber): boolean { - return this.value === other.value; - } - - masked(): string { - return this.value.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2'); - } -} - -// ============ ReferralCode ============ -export class ReferralCode { - constructor(public readonly value: string) { - if (!/^[A-Z0-9]{6}$/.test(value)) { - throw new DomainError('推荐码格式错误'); - } - } - - static generate(): ReferralCode { - const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; - let code = ''; - for (let i = 0; i < 6; i++) { - code += chars.charAt(Math.floor(Math.random() * chars.length)); - } - return new ReferralCode(code); - } - - static create(value: string): ReferralCode { - return new ReferralCode(value.toUpperCase()); - } - - equals(other: ReferralCode): boolean { - return this.value === other.value; - } -} - -// ============ ProvinceCode & CityCode ============ -export class ProvinceCode { - constructor(public readonly value: string) {} - - static create(value: string): ProvinceCode { - return new ProvinceCode(value || 'DEFAULT'); - } -} - -export class CityCode { - constructor(public readonly value: string) {} - - static create(value: string): CityCode { - return new CityCode(value || 'DEFAULT'); - } -} - -// ============ Mnemonic ============ -export class Mnemonic { - constructor(public readonly value: string) { - if (!bip39.validateMnemonic(value, wordlist)) { - throw new DomainError('助记词格式错误'); - } - } - - static generate(): Mnemonic { - const mnemonic = bip39.generateMnemonic(wordlist, 128); - return new Mnemonic(mnemonic); - } - - static create(value: string): Mnemonic { - return new Mnemonic(value); - } - - toSeed(): Uint8Array { - return bip39.mnemonicToSeedSync(this.value); - } - - getWords(): string[] { - return this.value.split(' '); - } - - equals(other: Mnemonic): boolean { - return this.value === other.value; - } -} - -// ============ DeviceInfo ============ -export class DeviceInfo { - private _lastActiveAt: Date; - - constructor( - public readonly deviceId: string, - public readonly deviceName: string, - public readonly addedAt: Date, - lastActiveAt: Date, - ) { - this._lastActiveAt = lastActiveAt; - } - - get lastActiveAt(): Date { - return this._lastActiveAt; - } - - updateActivity(): void { - this._lastActiveAt = new Date(); - } -} - -// ============ ChainType ============ -export enum ChainType { - KAVA = 'KAVA', - DST = 'DST', - BSC = 'BSC', -} - -export const CHAIN_CONFIG = { - [ChainType.KAVA]: { prefix: 'kava', derivationPath: "m/44'/459'/0'/0/0" }, - [ChainType.DST]: { prefix: 'dst', derivationPath: "m/44'/118'/0'/0/0" }, - [ChainType.BSC]: { prefix: '0x', derivationPath: "m/44'/60'/0'/0/0" }, -}; - -// ============ KYCInfo ============ -export class KYCInfo { - constructor( - public readonly realName: string, - public readonly idCardNumber: string, - public readonly idCardFrontUrl: string, - public readonly idCardBackUrl: string, - ) { - if (!realName || realName.length < 2) { - throw new DomainError('真实姓名不合法'); - } - if (!/^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[0-9Xx]$/.test(idCardNumber)) { - throw new DomainError('身份证号格式错误'); - } - } - - static create(params: { realName: string; idCardNumber: string; idCardFrontUrl: string; idCardBackUrl: string }): KYCInfo { - return new KYCInfo(params.realName, params.idCardNumber, params.idCardFrontUrl, params.idCardBackUrl); - } - - maskedIdCardNumber(): string { - return this.idCardNumber.replace(/(\d{6})\d{8}(\d{4})/, '$1********$2'); - } -} - -// ============ Enums ============ -export enum KYCStatus { - NOT_VERIFIED = 'NOT_VERIFIED', - PENDING = 'PENDING', - VERIFIED = 'VERIFIED', - REJECTED = 'REJECTED', -} - -export enum AccountStatus { - ACTIVE = 'ACTIVE', - FROZEN = 'FROZEN', - DEACTIVATED = 'DEACTIVATED', -} - -export enum AddressStatus { - ACTIVE = 'ACTIVE', - DISABLED = 'DISABLED', -} - -// ============ AddressId ============ -export class AddressId { - constructor(public readonly value: string) {} - - static generate(): AddressId { - return new AddressId(crypto.randomUUID()); - } - - static create(value: string): AddressId { - return new AddressId(value); - } -} - -// ============ MnemonicEncryption ============ -export class MnemonicEncryption { - static encrypt(mnemonic: string, key: string): string { - const derivedKey = this.deriveKey(key); - const iv = randomBytes(16); - const cipher = createCipheriv('aes-256-gcm', derivedKey, iv); - - let encrypted = cipher.update(mnemonic, 'utf8', 'hex'); - encrypted += cipher.final('hex'); - const authTag = cipher.getAuthTag(); - - return JSON.stringify({ - encrypted, - authTag: authTag.toString('hex'), - iv: iv.toString('hex'), - }); - } - - static decrypt(encryptedData: string, key: string): string { - const { encrypted, authTag, iv } = JSON.parse(encryptedData); - const derivedKey = this.deriveKey(key); - const decipher = createDecipheriv('aes-256-gcm', derivedKey, Buffer.from(iv, 'hex')); - decipher.setAuthTag(Buffer.from(authTag, 'hex')); - - let decrypted = decipher.update(encrypted, 'hex', 'utf8'); - decrypted += decipher.final('utf8'); - return decrypted; - } - - private static deriveKey(password: string): Buffer { - return scryptSync(password, 'rwa-wallet-salt', 32); - } -} diff --git a/backend/services/identity-service/identity-service/src/domain/value-objects/kyc-info.vo.ts b/backend/services/identity-service/identity-service/src/domain/value-objects/kyc-info.vo.ts deleted file mode 100644 index 2b1b14bb..00000000 --- a/backend/services/identity-service/identity-service/src/domain/value-objects/kyc-info.vo.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { DomainError } from '@/shared/exceptions/domain.exception'; - -export class KYCInfo { - constructor( - public readonly realName: string, - public readonly idCardNumber: string, - public readonly idCardFrontUrl: string, - public readonly idCardBackUrl: string, - ) { - if (!realName || realName.length < 2) { - throw new DomainError('真实姓名不合法'); - } - if (!/^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[0-9Xx]$/.test(idCardNumber)) { - throw new DomainError('身份证号格式错误'); - } - } - - static create(params: { realName: string; idCardNumber: string; idCardFrontUrl: string; idCardBackUrl: string }): KYCInfo { - return new KYCInfo(params.realName, params.idCardNumber, params.idCardFrontUrl, params.idCardBackUrl); - } - - maskedIdCardNumber(): string { - return this.idCardNumber.replace(/(\d{6})\d{8}(\d{4})/, '$1********$2'); - } -} diff --git a/backend/services/identity-service/identity-service/src/domain/value-objects/mnemonic.vo.ts b/backend/services/identity-service/identity-service/src/domain/value-objects/mnemonic.vo.ts deleted file mode 100644 index 7740b289..00000000 --- a/backend/services/identity-service/identity-service/src/domain/value-objects/mnemonic.vo.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { DomainError } from '@/shared/exceptions/domain.exception'; -import * as bip39 from '@scure/bip39'; -import { wordlist } from '@scure/bip39/wordlists/english'; - -export class Mnemonic { - constructor(public readonly value: string) { - if (!bip39.validateMnemonic(value, wordlist)) { - throw new DomainError('助记词格式错误'); - } - } - - static generate(): Mnemonic { - const mnemonic = bip39.generateMnemonic(wordlist, 128); - return new Mnemonic(mnemonic); - } - - static create(value: string): Mnemonic { - return new Mnemonic(value); - } - - toSeed(): Uint8Array { - return bip39.mnemonicToSeedSync(this.value); - } - - getWords(): string[] { - return this.value.split(' '); - } - - equals(other: Mnemonic): boolean { - return this.value === other.value; - } -} diff --git a/backend/services/identity-service/identity-service/src/domain/value-objects/phone-number.vo.ts b/backend/services/identity-service/identity-service/src/domain/value-objects/phone-number.vo.ts deleted file mode 100644 index 97d18118..00000000 --- a/backend/services/identity-service/identity-service/src/domain/value-objects/phone-number.vo.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { DomainError } from '@/shared/exceptions/domain.exception'; - -export class PhoneNumber { - constructor(public readonly value: string) { - if (!/^1[3-9]\d{9}$/.test(value)) { - throw new DomainError('手机号格式错误'); - } - } - - static create(value: string): PhoneNumber { - return new PhoneNumber(value); - } - - equals(other: PhoneNumber): boolean { - return this.value === other.value; - } - - masked(): string { - return this.value.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2'); - } -} diff --git a/backend/services/identity-service/identity-service/src/domain/value-objects/referral-code.vo.ts b/backend/services/identity-service/identity-service/src/domain/value-objects/referral-code.vo.ts deleted file mode 100644 index 230963fe..00000000 --- a/backend/services/identity-service/identity-service/src/domain/value-objects/referral-code.vo.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { DomainError } from '@/shared/exceptions/domain.exception'; - -export class ReferralCode { - constructor(public readonly value: string) { - if (!/^[A-Z0-9]{6}$/.test(value)) { - throw new DomainError('推荐码格式错误'); - } - } - - static generate(): ReferralCode { - const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; - let code = ''; - for (let i = 0; i < 6; i++) { - code += chars.charAt(Math.floor(Math.random() * chars.length)); - } - return new ReferralCode(code); - } - - static create(value: string): ReferralCode { - return new ReferralCode(value.toUpperCase()); - } - - equals(other: ReferralCode): boolean { - return this.value === other.value; - } -} diff --git a/backend/services/identity-service/identity-service/src/infrastructure/external/blockchain/blockchain.module.ts b/backend/services/identity-service/identity-service/src/infrastructure/external/blockchain/blockchain.module.ts deleted file mode 100644 index d33a9eee..00000000 --- a/backend/services/identity-service/identity-service/src/infrastructure/external/blockchain/blockchain.module.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Module } from '@nestjs/common'; -import { WalletGeneratorServiceImpl } from './wallet-generator.service.impl'; - -@Module({ - providers: [WalletGeneratorServiceImpl], - exports: [WalletGeneratorServiceImpl], -}) -export class BlockchainModule {} diff --git a/backend/services/identity-service/identity-service/src/infrastructure/external/blockchain/wallet-generator.service.impl.ts b/backend/services/identity-service/identity-service/src/infrastructure/external/blockchain/wallet-generator.service.impl.ts deleted file mode 100644 index f1d3d40a..00000000 --- a/backend/services/identity-service/identity-service/src/infrastructure/external/blockchain/wallet-generator.service.impl.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { createHash } from 'crypto'; -import { WalletAddress } from '@/domain/entities/wallet-address.entity'; -import { ChainType, Mnemonic, UserId } from '@/domain/value-objects'; - -@Injectable() -export class WalletGeneratorServiceImpl { - generateWalletSystem(params: { userId: UserId; deviceId: string }): { - mnemonic: Mnemonic; - wallets: Map; - } { - const mnemonic = Mnemonic.generate(); - const encryptionKey = this.deriveEncryptionKey(params.deviceId, params.userId.value); - - const wallets = new Map(); - const chains = [ChainType.KAVA, ChainType.DST, ChainType.BSC]; - - for (const chainType of chains) { - const wallet = WalletAddress.createFromMnemonic({ - userId: params.userId, - chainType, - mnemonic, - encryptionKey, - }); - wallets.set(chainType, wallet); - } - - return { mnemonic, wallets }; - } - - recoverWalletSystem(params: { userId: UserId; mnemonic: Mnemonic; deviceId: string }): Map { - const encryptionKey = this.deriveEncryptionKey(params.deviceId, params.userId.value); - const wallets = new Map(); - const chains = [ChainType.KAVA, ChainType.DST, ChainType.BSC]; - - for (const chainType of chains) { - const wallet = WalletAddress.createFromMnemonic({ - userId: params.userId, - chainType, - mnemonic: params.mnemonic, - encryptionKey, - }); - wallets.set(chainType, wallet); - } - - return wallets; - } - - private deriveEncryptionKey(deviceId: string, userId: string): string { - const input = `${deviceId}:${userId}`; - return createHash('sha256').update(input).digest('hex'); - } -} diff --git a/backend/services/identity-service/identity-service/src/infrastructure/external/sms/sms.module.ts b/backend/services/identity-service/identity-service/src/infrastructure/external/sms/sms.module.ts deleted file mode 100644 index cc282dd4..00000000 --- a/backend/services/identity-service/identity-service/src/infrastructure/external/sms/sms.module.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Module } from '@nestjs/common'; -import { SmsService } from './sms.service'; - -@Module({ - providers: [SmsService], - exports: [SmsService], -}) -export class SmsModule {} diff --git a/backend/services/identity-service/identity-service/src/infrastructure/external/sms/sms.service.ts b/backend/services/identity-service/identity-service/src/infrastructure/external/sms/sms.service.ts deleted file mode 100644 index 5923a40b..00000000 --- a/backend/services/identity-service/identity-service/src/infrastructure/external/sms/sms.service.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; - -@Injectable() -export class SmsService { - constructor(private readonly configService: ConfigService) {} - - async sendSms(phoneNumber: string, content: string): Promise { - const apiUrl = this.configService.get('SMS_API_URL'); - const apiKey = this.configService.get('SMS_API_KEY'); - - // 实际项目中调用SMS API - console.log(`[SMS] Sending to ${phoneNumber}: ${content}`); - - // 模拟发送成功 - return true; - } - - async sendVerificationCode(phoneNumber: string, code: string): Promise { - const content = `您的验证码是${code},5分钟内有效。`; - return this.sendSms(phoneNumber, content); - } -} diff --git a/backend/services/identity-service/identity-service/src/infrastructure/infrastructure.module.ts b/backend/services/identity-service/identity-service/src/infrastructure/infrastructure.module.ts deleted file mode 100644 index 4c53bcb2..00000000 --- a/backend/services/identity-service/identity-service/src/infrastructure/infrastructure.module.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Module, Global } from '@nestjs/common'; -import { PrismaService } from './persistence/prisma/prisma.service'; -import { UserAccountRepositoryImpl } from './persistence/repositories/user-account.repository.impl'; -import { UserAccountMapper } from './persistence/mappers/user-account.mapper'; -import { RedisService } from './redis/redis.service'; -import { EventPublisherService } from './kafka/event-publisher.service'; -import { SmsService } from './external/sms/sms.service'; -import { WalletGeneratorServiceImpl } from './external/blockchain/wallet-generator.service.impl'; - -@Global() -@Module({ - providers: [ - PrismaService, - UserAccountRepositoryImpl, - UserAccountMapper, - RedisService, - EventPublisherService, - SmsService, - WalletGeneratorServiceImpl, - ], - exports: [ - PrismaService, - UserAccountRepositoryImpl, - UserAccountMapper, - RedisService, - EventPublisherService, - SmsService, - WalletGeneratorServiceImpl, - ], -}) -export class InfrastructureModule {} diff --git a/backend/services/identity-service/identity-service/src/infrastructure/kafka/event-publisher.service.ts b/backend/services/identity-service/identity-service/src/infrastructure/kafka/event-publisher.service.ts deleted file mode 100644 index dcfd796a..00000000 --- a/backend/services/identity-service/identity-service/src/infrastructure/kafka/event-publisher.service.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import { Kafka, Producer, Consumer, logLevel } from 'kafkajs'; -import { DomainEvent } from '@/domain/events'; - -@Injectable() -export class EventPublisherService implements OnModuleInit, OnModuleDestroy { - private kafka: Kafka; - private producer: Producer; - - constructor(private readonly configService: ConfigService) { - this.kafka = new Kafka({ - clientId: this.configService.get('KAFKA_CLIENT_ID', 'identity-service'), - brokers: (this.configService.get('KAFKA_BROKERS', 'localhost:9092')).split(','), - logLevel: logLevel.WARN, - }); - this.producer = this.kafka.producer(); - } - - async onModuleInit() { - await this.producer.connect(); - } - - async onModuleDestroy() { - await this.producer.disconnect(); - } - - async publish(event: DomainEvent): Promise { - await this.producer.send({ - topic: `identity.${event.eventType}`, - messages: [ - { - key: event.eventId, - value: JSON.stringify({ - eventId: event.eventId, - eventType: event.eventType, - occurredAt: event.occurredAt.toISOString(), - payload: (event as any).payload, - }), - }, - ], - }); - } - - async publishAll(events: DomainEvent[]): Promise { - for (const event of events) { - await this.publish(event); - } - } -} - -@Injectable() -export class KafkaModule {} diff --git a/backend/services/identity-service/identity-service/src/infrastructure/kafka/kafka.module.ts b/backend/services/identity-service/identity-service/src/infrastructure/kafka/kafka.module.ts deleted file mode 100644 index 98309286..00000000 --- a/backend/services/identity-service/identity-service/src/infrastructure/kafka/kafka.module.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Module } from '@nestjs/common'; -import { EventPublisherService } from './event-publisher.service'; - -@Module({ - providers: [EventPublisherService], - exports: [EventPublisherService], -}) -export class KafkaModule {} diff --git a/backend/services/identity-service/identity-service/src/infrastructure/persistence/entities/user-account.entity.ts b/backend/services/identity-service/identity-service/src/infrastructure/persistence/entities/user-account.entity.ts deleted file mode 100644 index b1fb63ea..00000000 --- a/backend/services/identity-service/identity-service/src/infrastructure/persistence/entities/user-account.entity.ts +++ /dev/null @@ -1,44 +0,0 @@ -// Prisma Entity Types - 用于Mapper转换 -export interface UserAccountEntity { - userId: bigint; - accountSequence: bigint; - phoneNumber: string | null; - nickname: string; - avatarUrl: string | null; - inviterSequence: bigint | null; - referralCode: string; - provinceCode: string; - cityCode: string; - address: string | null; - kycStatus: string; - realName: string | null; - idCardNumber: string | null; - idCardFrontUrl: string | null; - idCardBackUrl: string | null; - kycVerifiedAt: Date | null; - status: string; - registeredAt: Date; - lastLoginAt: Date | null; - updatedAt: Date; - devices?: UserDeviceEntity[]; - walletAddresses?: WalletAddressEntity[]; -} - -export interface UserDeviceEntity { - id: bigint; - userId: bigint; - deviceId: string; - deviceName: string | null; - addedAt: Date; - lastActiveAt: Date; -} - -export interface WalletAddressEntity { - addressId: bigint; - userId: bigint; - chainType: string; - address: string; - encryptedMnemonic: string | null; - status: string; - boundAt: Date; -} diff --git a/backend/services/identity-service/identity-service/src/infrastructure/persistence/entities/user-device.entity.ts b/backend/services/identity-service/identity-service/src/infrastructure/persistence/entities/user-device.entity.ts deleted file mode 100644 index 3d630d46..00000000 --- a/backend/services/identity-service/identity-service/src/infrastructure/persistence/entities/user-device.entity.ts +++ /dev/null @@ -1,8 +0,0 @@ -export interface UserDeviceEntity { - id: bigint; - userId: bigint; - deviceId: string; - deviceName: string | null; - addedAt: Date; - lastActiveAt: Date; -} diff --git a/backend/services/identity-service/identity-service/src/infrastructure/persistence/entities/wallet-address.entity.ts b/backend/services/identity-service/identity-service/src/infrastructure/persistence/entities/wallet-address.entity.ts deleted file mode 100644 index d4a5b402..00000000 --- a/backend/services/identity-service/identity-service/src/infrastructure/persistence/entities/wallet-address.entity.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface WalletAddressEntity { - addressId: bigint; - userId: bigint; - chainType: string; - address: string; - encryptedMnemonic: string | null; - status: string; - boundAt: Date; -} diff --git a/backend/services/identity-service/identity-service/src/infrastructure/persistence/mappers/user-account.mapper.ts b/backend/services/identity-service/identity-service/src/infrastructure/persistence/mappers/user-account.mapper.ts deleted file mode 100644 index 52882eb8..00000000 --- a/backend/services/identity-service/identity-service/src/infrastructure/persistence/mappers/user-account.mapper.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { UserAccount } from '@/domain/aggregates/user-account/user-account.aggregate'; -import { WalletAddress } from '@/domain/entities/wallet-address.entity'; -import { DeviceInfo, KYCInfo, KYCStatus, AccountStatus, ChainType, AddressStatus } from '@/domain/value-objects'; -import { UserAccountEntity } from '../entities/user-account.entity'; - -@Injectable() -export class UserAccountMapper { - toDomain(entity: UserAccountEntity): UserAccount { - const devices = (entity.devices || []).map( - (d) => new DeviceInfo(d.deviceId, d.deviceName || '未命名设备', d.addedAt, d.lastActiveAt), - ); - - const wallets = (entity.walletAddresses || []).map((w) => - WalletAddress.reconstruct({ - addressId: w.addressId.toString(), - userId: w.userId.toString(), - chainType: w.chainType as ChainType, - address: w.address, - encryptedMnemonic: w.encryptedMnemonic || '', - status: w.status as AddressStatus, - boundAt: w.boundAt, - }), - ); - - const kycInfo = - entity.realName && entity.idCardNumber - ? KYCInfo.create({ - realName: entity.realName, - idCardNumber: entity.idCardNumber, - idCardFrontUrl: entity.idCardFrontUrl || '', - idCardBackUrl: entity.idCardBackUrl || '', - }) - : null; - - return UserAccount.reconstruct({ - userId: entity.userId.toString(), - accountSequence: Number(entity.accountSequence), - devices, - phoneNumber: entity.phoneNumber, - nickname: entity.nickname, - avatarUrl: entity.avatarUrl, - inviterSequence: entity.inviterSequence ? Number(entity.inviterSequence) : null, - referralCode: entity.referralCode, - province: entity.provinceCode, - city: entity.cityCode, - address: entity.address, - walletAddresses: wallets, - kycInfo, - kycStatus: entity.kycStatus as KYCStatus, - status: entity.status as AccountStatus, - registeredAt: entity.registeredAt, - lastLoginAt: entity.lastLoginAt, - updatedAt: entity.updatedAt, - }); - } -} diff --git a/backend/services/identity-service/identity-service/src/infrastructure/persistence/prisma/migrations/.gitkeep b/backend/services/identity-service/identity-service/src/infrastructure/persistence/prisma/migrations/.gitkeep deleted file mode 100644 index b87434af..00000000 --- a/backend/services/identity-service/identity-service/src/infrastructure/persistence/prisma/migrations/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -# Prisma migrations will be stored here diff --git a/backend/services/identity-service/identity-service/src/infrastructure/persistence/prisma/prisma.service.ts b/backend/services/identity-service/identity-service/src/infrastructure/persistence/prisma/prisma.service.ts deleted file mode 100644 index bb6565f3..00000000 --- a/backend/services/identity-service/identity-service/src/infrastructure/persistence/prisma/prisma.service.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'; -import { PrismaClient } from '@prisma/client'; - -@Injectable() -export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy { - async onModuleInit() { - await this.$connect(); - } - - async onModuleDestroy() { - await this.$disconnect(); - } -} diff --git a/backend/services/identity-service/identity-service/src/infrastructure/persistence/prisma/schema.prisma b/backend/services/identity-service/identity-service/src/infrastructure/persistence/prisma/schema.prisma deleted file mode 100644 index e15bef5e..00000000 --- a/backend/services/identity-service/identity-service/src/infrastructure/persistence/prisma/schema.prisma +++ /dev/null @@ -1,133 +0,0 @@ -generator client { - provider = "prisma-client-js" -} - -datasource db { - provider = "postgresql" - url = env("DATABASE_URL") -} - -model UserAccount { - userId BigInt @id @default(autoincrement()) @map("user_id") - accountSequence BigInt @unique @map("account_sequence") - - phoneNumber String? @unique @map("phone_number") @db.VarChar(20) - nickname String @db.VarChar(100) - avatarUrl String? @map("avatar_url") @db.VarChar(500) - - inviterSequence BigInt? @map("inviter_sequence") - referralCode String @unique @map("referral_code") @db.VarChar(10) - - provinceCode String @map("province_code") @db.VarChar(10) - cityCode String @map("city_code") @db.VarChar(10) - address String? @db.VarChar(500) - - kycStatus String @default("NOT_VERIFIED") @map("kyc_status") @db.VarChar(20) - realName String? @map("real_name") @db.VarChar(100) - idCardNumber String? @map("id_card_number") @db.VarChar(20) - idCardFrontUrl String? @map("id_card_front_url") @db.VarChar(500) - idCardBackUrl String? @map("id_card_back_url") @db.VarChar(500) - kycVerifiedAt DateTime? @map("kyc_verified_at") - - status String @default("ACTIVE") @db.VarChar(20) - - registeredAt DateTime @default(now()) @map("registered_at") - lastLoginAt DateTime? @map("last_login_at") - updatedAt DateTime @updatedAt @map("updated_at") - - devices UserDevice[] - walletAddresses WalletAddress[] - - @@index([phoneNumber], name: "idx_phone") - @@index([accountSequence], name: "idx_sequence") - @@index([referralCode], name: "idx_referral_code") - @@index([inviterSequence], name: "idx_inviter") - @@index([provinceCode, cityCode], name: "idx_province_city") - @@index([kycStatus], name: "idx_kyc_status") - @@index([status], name: "idx_status") - @@map("user_accounts") -} - -model UserDevice { - id BigInt @id @default(autoincrement()) - userId BigInt @map("user_id") - deviceId String @map("device_id") @db.VarChar(100) - deviceName String? @map("device_name") @db.VarChar(100) - - addedAt DateTime @default(now()) @map("added_at") - lastActiveAt DateTime @default(now()) @map("last_active_at") - - user UserAccount @relation(fields: [userId], references: [userId], onDelete: Cascade) - - @@unique([userId, deviceId], name: "uk_user_device") - @@index([deviceId], name: "idx_device") - @@index([userId], name: "idx_user") - @@index([lastActiveAt], name: "idx_last_active") - @@map("user_devices") -} - -model WalletAddress { - addressId BigInt @id @default(autoincrement()) @map("address_id") - userId BigInt @map("user_id") - - chainType String @map("chain_type") @db.VarChar(20) - address String @db.VarChar(100) - - encryptedMnemonic String? @map("encrypted_mnemonic") @db.Text - - status String @default("ACTIVE") @db.VarChar(20) - - boundAt DateTime @default(now()) @map("bound_at") - - user UserAccount @relation(fields: [userId], references: [userId], onDelete: Cascade) - - @@unique([userId, chainType], name: "uk_user_chain") - @@unique([chainType, address], name: "uk_chain_address") - @@index([userId], name: "idx_wallet_user") - @@index([address], name: "idx_address") - @@map("wallet_addresses") -} - -model AccountSequenceGenerator { - id Int @id @default(1) - currentSequence BigInt @default(0) @map("current_sequence") - updatedAt DateTime @updatedAt @map("updated_at") - - @@map("account_sequence_generator") -} - -model UserEvent { - eventId BigInt @id @default(autoincrement()) @map("event_id") - eventType String @map("event_type") @db.VarChar(50) - - aggregateId String @map("aggregate_id") @db.VarChar(100) - aggregateType String @map("aggregate_type") @db.VarChar(50) - - eventData Json @map("event_data") - - userId BigInt? @map("user_id") - occurredAt DateTime @default(now()) @map("occurred_at") @db.Timestamp(6) - version Int @default(1) - - @@index([aggregateType, aggregateId], name: "idx_aggregate") - @@index([eventType], name: "idx_event_type") - @@index([userId], name: "idx_event_user") - @@index([occurredAt], name: "idx_occurred") - @@map("user_events") -} - -model DeviceToken { - id BigInt @id @default(autoincrement()) - userId BigInt @map("user_id") - deviceId String @map("device_id") @db.VarChar(100) - - refreshTokenHash String @unique @map("refresh_token_hash") @db.VarChar(64) - - expiresAt DateTime @map("expires_at") - createdAt DateTime @default(now()) @map("created_at") - revokedAt DateTime? @map("revoked_at") - - @@index([userId, deviceId], name: "idx_user_device_token") - @@index([expiresAt], name: "idx_expires") - @@map("device_tokens") -} diff --git a/backend/services/identity-service/identity-service/src/infrastructure/persistence/repositories/user-account.repository.impl.ts b/backend/services/identity-service/identity-service/src/infrastructure/persistence/repositories/user-account.repository.impl.ts deleted file mode 100644 index f02341ca..00000000 --- a/backend/services/identity-service/identity-service/src/infrastructure/persistence/repositories/user-account.repository.impl.ts +++ /dev/null @@ -1,233 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { PrismaService } from '@/infrastructure/persistence/prisma/prisma.service'; -import { UserAccountRepository, Pagination } from '@/domain/repositories/user-account.repository.interface'; -import { UserAccount } from '@/domain/aggregates/user-account/user-account.aggregate'; -import { WalletAddress } from '@/domain/entities/wallet-address.entity'; -import { - UserId, AccountSequence, PhoneNumber, ReferralCode, ChainType, - AccountStatus, KYCStatus, DeviceInfo, KYCInfo, AddressStatus, -} from '@/domain/value-objects'; - -@Injectable() -export class UserAccountRepositoryImpl implements UserAccountRepository { - constructor(private readonly prisma: PrismaService) {} - - async save(account: UserAccount): Promise { - const devices = account.getAllDevices(); - const wallets = account.getAllWalletAddresses(); - - await this.prisma.$transaction(async (tx) => { - await tx.userAccount.upsert({ - where: { userId: BigInt(account.userId.value) }, - create: { - userId: BigInt(account.userId.value), - accountSequence: BigInt(account.accountSequence.value), - phoneNumber: account.phoneNumber?.value || null, - nickname: account.nickname, - avatarUrl: account.avatarUrl, - inviterSequence: account.inviterSequence ? BigInt(account.inviterSequence.value) : null, - referralCode: account.referralCode.value, - provinceCode: account.province.value, - cityCode: account.city.value, - address: account.addressDetail, - kycStatus: account.kycStatus, - realName: account.kycInfo?.realName || null, - idCardNumber: account.kycInfo?.idCardNumber || null, - idCardFrontUrl: account.kycInfo?.idCardFrontUrl || null, - idCardBackUrl: account.kycInfo?.idCardBackUrl || null, - status: account.status, - registeredAt: account.registeredAt, - lastLoginAt: account.lastLoginAt, - }, - update: { - phoneNumber: account.phoneNumber?.value || null, - nickname: account.nickname, - avatarUrl: account.avatarUrl, - provinceCode: account.province.value, - cityCode: account.city.value, - address: account.addressDetail, - kycStatus: account.kycStatus, - realName: account.kycInfo?.realName || null, - idCardNumber: account.kycInfo?.idCardNumber || null, - idCardFrontUrl: account.kycInfo?.idCardFrontUrl || null, - idCardBackUrl: account.kycInfo?.idCardBackUrl || null, - status: account.status, - lastLoginAt: account.lastLoginAt, - }, - }); - - // Sync devices - await tx.userDevice.deleteMany({ where: { userId: BigInt(account.userId.value) } }); - if (devices.length > 0) { - await tx.userDevice.createMany({ - data: devices.map((d) => ({ - userId: BigInt(account.userId.value), - deviceId: d.deviceId, - deviceName: d.deviceName, - addedAt: d.addedAt, - lastActiveAt: d.lastActiveAt, - })), - }); - } - }); - } - - async saveWallets(userId: UserId, wallets: WalletAddress[]): Promise { - await this.prisma.walletAddress.createMany({ - data: wallets.map((w) => ({ - userId: BigInt(userId.value), - chainType: w.chainType, - address: w.address, - encryptedMnemonic: w.encryptedMnemonic, - status: w.status, - boundAt: w.boundAt, - })), - skipDuplicates: true, - }); - } - - async findById(userId: UserId): Promise { - const data = await this.prisma.userAccount.findUnique({ - where: { userId: BigInt(userId.value) }, - include: { devices: true, walletAddresses: true }, - }); - return data ? this.toDomain(data) : null; - } - - async findByAccountSequence(sequence: AccountSequence): Promise { - const data = await this.prisma.userAccount.findUnique({ - where: { accountSequence: BigInt(sequence.value) }, - include: { devices: true, walletAddresses: true }, - }); - return data ? this.toDomain(data) : null; - } - - async findByDeviceId(deviceId: string): Promise { - const device = await this.prisma.userDevice.findFirst({ where: { deviceId } }); - if (!device) return null; - return this.findById(UserId.create(device.userId.toString())); - } - - async findByPhoneNumber(phoneNumber: PhoneNumber): Promise { - const data = await this.prisma.userAccount.findUnique({ - where: { phoneNumber: phoneNumber.value }, - include: { devices: true, walletAddresses: true }, - }); - return data ? this.toDomain(data) : null; - } - - async findByReferralCode(referralCode: ReferralCode): Promise { - const data = await this.prisma.userAccount.findUnique({ - where: { referralCode: referralCode.value }, - include: { devices: true, walletAddresses: true }, - }); - return data ? this.toDomain(data) : null; - } - - async findByWalletAddress(chainType: ChainType, address: string): Promise { - const wallet = await this.prisma.walletAddress.findUnique({ - where: { uk_chain_address: { chainType, address } }, - }); - if (!wallet) return null; - return this.findById(UserId.create(wallet.userId.toString())); - } - - async getMaxAccountSequence(): Promise { - const result = await this.prisma.userAccount.aggregate({ _max: { accountSequence: true } }); - return result._max.accountSequence ? AccountSequence.create(Number(result._max.accountSequence)) : null; - } - - async getNextAccountSequence(): Promise { - const result = await this.prisma.$transaction(async (tx) => { - const updated = await tx.accountSequenceGenerator.update({ - where: { id: 1 }, - data: { currentSequence: { increment: 1 } }, - }); - return updated.currentSequence; - }); - return AccountSequence.create(Number(result)); - } - - async findUsers( - filters?: { status?: AccountStatus; kycStatus?: KYCStatus; province?: string; city?: string; keyword?: string }, - pagination?: Pagination, - ): Promise { - const where: any = {}; - if (filters?.status) where.status = filters.status; - if (filters?.kycStatus) where.kycStatus = filters.kycStatus; - if (filters?.province) where.provinceCode = filters.province; - if (filters?.city) where.cityCode = filters.city; - if (filters?.keyword) { - where.OR = [ - { nickname: { contains: filters.keyword } }, - { phoneNumber: { contains: filters.keyword } }, - ]; - } - - const data = await this.prisma.userAccount.findMany({ - where, - include: { devices: true, walletAddresses: true }, - skip: pagination ? (pagination.page - 1) * pagination.limit : undefined, - take: pagination?.limit, - orderBy: { registeredAt: 'desc' }, - }); - - return data.map((d) => this.toDomain(d)); - } - - async countUsers(filters?: { status?: AccountStatus; kycStatus?: KYCStatus }): Promise { - const where: any = {}; - if (filters?.status) where.status = filters.status; - if (filters?.kycStatus) where.kycStatus = filters.kycStatus; - return this.prisma.userAccount.count({ where }); - } - - private toDomain(data: any): UserAccount { - const devices = data.devices.map( - (d: any) => new DeviceInfo(d.deviceId, d.deviceName || '未命名设备', d.addedAt, d.lastActiveAt), - ); - - const wallets = data.walletAddresses.map((w: any) => - WalletAddress.reconstruct({ - addressId: w.addressId.toString(), - userId: w.userId.toString(), - chainType: w.chainType as ChainType, - address: w.address, - encryptedMnemonic: w.encryptedMnemonic || '', - status: w.status as AddressStatus, - boundAt: w.boundAt, - }), - ); - - const kycInfo = - data.realName && data.idCardNumber - ? KYCInfo.create({ - realName: data.realName, - idCardNumber: data.idCardNumber, - idCardFrontUrl: data.idCardFrontUrl || '', - idCardBackUrl: data.idCardBackUrl || '', - }) - : null; - - return UserAccount.reconstruct({ - userId: data.userId.toString(), - accountSequence: Number(data.accountSequence), - devices, - phoneNumber: data.phoneNumber, - nickname: data.nickname, - avatarUrl: data.avatarUrl, - inviterSequence: data.inviterSequence ? Number(data.inviterSequence) : null, - referralCode: data.referralCode, - province: data.provinceCode, - city: data.cityCode, - address: data.address, - walletAddresses: wallets, - kycInfo, - kycStatus: data.kycStatus as KYCStatus, - status: data.status as AccountStatus, - registeredAt: data.registeredAt, - lastLoginAt: data.lastLoginAt, - updatedAt: data.updatedAt, - }); - } -} diff --git a/backend/services/identity-service/identity-service/src/infrastructure/redis/redis.module.ts b/backend/services/identity-service/identity-service/src/infrastructure/redis/redis.module.ts deleted file mode 100644 index b4958682..00000000 --- a/backend/services/identity-service/identity-service/src/infrastructure/redis/redis.module.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Module } from '@nestjs/common'; -import { RedisService } from './redis.service'; - -@Module({ - providers: [RedisService], - exports: [RedisService], -}) -export class RedisModule {} diff --git a/backend/services/identity-service/identity-service/src/infrastructure/redis/redis.service.ts b/backend/services/identity-service/identity-service/src/infrastructure/redis/redis.service.ts deleted file mode 100644 index ddc6e048..00000000 --- a/backend/services/identity-service/identity-service/src/infrastructure/redis/redis.service.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Injectable, OnModuleDestroy } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import Redis from 'ioredis'; - -@Injectable() -export class RedisService implements OnModuleDestroy { - private readonly client: Redis; - - constructor(private readonly configService: ConfigService) { - this.client = new Redis({ - host: this.configService.get('REDIS_HOST', 'localhost'), - port: this.configService.get('REDIS_PORT', 6379), - password: this.configService.get('REDIS_PASSWORD') || undefined, - db: this.configService.get('REDIS_DB', 0), - }); - } - - async get(key: string): Promise { - return this.client.get(key); - } - - async set(key: string, value: string, ttlSeconds?: number): Promise { - if (ttlSeconds) { - await this.client.set(key, value, 'EX', ttlSeconds); - } else { - await this.client.set(key, value); - } - } - - async delete(key: string): Promise { - await this.client.del(key); - } - - async exists(key: string): Promise { - const result = await this.client.exists(key); - return result === 1; - } - - async incr(key: string): Promise { - return this.client.incr(key); - } - - async expire(key: string, seconds: number): Promise { - await this.client.expire(key, seconds); - } - - onModuleDestroy() { - this.client.disconnect(); - } -} diff --git a/backend/services/identity-service/identity-service/src/main.ts b/backend/services/identity-service/identity-service/src/main.ts deleted file mode 100644 index 61e85873..00000000 --- a/backend/services/identity-service/identity-service/src/main.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { NestFactory } from '@nestjs/core'; -import { ValidationPipe } from '@nestjs/common'; -import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; -import { AppModule } from './app.module'; - -async function bootstrap() { - const app = await NestFactory.create(AppModule); - - // Global prefix - app.setGlobalPrefix('api/v1'); - - // Validation - app.useGlobalPipes( - new ValidationPipe({ - whitelist: true, - forbidNonWhitelisted: true, - transform: true, - transformOptions: { enableImplicitConversion: true }, - }), - ); - - // CORS - app.enableCors({ - origin: '*', - methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', - credentials: true, - }); - - // Swagger - const config = new DocumentBuilder() - .setTitle('Identity Service API') - .setDescription('RWA用户身份服务API') - .setVersion('2.0.0') - .addBearerAuth() - .build(); - const document = SwaggerModule.createDocument(app, config); - SwaggerModule.setup('api/docs', app, document); - - const port = process.env.APP_PORT || 3000; - await app.listen(port); - console.log(`Identity Service is running on port ${port}`); - console.log(`Swagger docs: http://localhost:${port}/api/docs`); -} - -bootstrap(); diff --git a/backend/services/identity-service/identity-service/src/shared/decorators/current-user.decorator.ts b/backend/services/identity-service/identity-service/src/shared/decorators/current-user.decorator.ts deleted file mode 100644 index 7c59a823..00000000 --- a/backend/services/identity-service/identity-service/src/shared/decorators/current-user.decorator.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { createParamDecorator, ExecutionContext } from '@nestjs/common'; -import { CurrentUserData } from '@/shared/guards/jwt-auth.guard'; - -export const CurrentUser = createParamDecorator( - (data: keyof CurrentUserData | undefined, ctx: ExecutionContext): CurrentUserData | string | number => { - const request = ctx.switchToHttp().getRequest(); - const user = request.user as CurrentUserData; - return data ? user?.[data] : user; - }, -); diff --git a/backend/services/identity-service/identity-service/src/shared/decorators/public.decorator.ts b/backend/services/identity-service/identity-service/src/shared/decorators/public.decorator.ts deleted file mode 100644 index b3845e12..00000000 --- a/backend/services/identity-service/identity-service/src/shared/decorators/public.decorator.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { SetMetadata } from '@nestjs/common'; - -export const IS_PUBLIC_KEY = 'isPublic'; -export const Public = () => SetMetadata(IS_PUBLIC_KEY, true); diff --git a/backend/services/identity-service/identity-service/src/shared/exceptions/application.exception.ts b/backend/services/identity-service/identity-service/src/shared/exceptions/application.exception.ts deleted file mode 100644 index b778a858..00000000 --- a/backend/services/identity-service/identity-service/src/shared/exceptions/application.exception.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { HttpException, HttpStatus } from '@nestjs/common'; - -export class ApplicationException extends HttpException { - constructor( - message: string, - public readonly code?: string, - status: HttpStatus = HttpStatus.BAD_REQUEST, - ) { - super({ message, code, success: false }, status); - } -} diff --git a/backend/services/identity-service/identity-service/src/shared/exceptions/domain.exception.ts b/backend/services/identity-service/identity-service/src/shared/exceptions/domain.exception.ts deleted file mode 100644 index ff7e0190..00000000 --- a/backend/services/identity-service/identity-service/src/shared/exceptions/domain.exception.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { HttpException, HttpStatus } from '@nestjs/common'; - -export class DomainError extends Error { - constructor(message: string) { - super(message); - this.name = 'DomainError'; - } -} - -export class ApplicationError extends Error { - constructor( - message: string, - public readonly code?: string, - ) { - super(message); - this.name = 'ApplicationError'; - } -} - -export class BusinessException extends HttpException { - constructor( - message: string, - public readonly code?: string, - status: HttpStatus = HttpStatus.BAD_REQUEST, - ) { - super({ message, code, success: false }, status); - } -} - -export class UnauthorizedException extends HttpException { - constructor(message: string = '未授权访问') { - super({ message, success: false }, HttpStatus.UNAUTHORIZED); - } -} - -export class NotFoundException extends HttpException { - constructor(message: string = '资源不存在') { - super({ message, success: false }, HttpStatus.NOT_FOUND); - } -} diff --git a/backend/services/identity-service/identity-service/src/shared/filters/domain-exception.filter.ts b/backend/services/identity-service/identity-service/src/shared/filters/domain-exception.filter.ts deleted file mode 100644 index cd9c9fa8..00000000 --- a/backend/services/identity-service/identity-service/src/shared/filters/domain-exception.filter.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ExceptionFilter, Catch, ArgumentsHost, HttpStatus } from '@nestjs/common'; -import { Response } from 'express'; -import { DomainError } from '@/shared/exceptions/domain.exception'; - -@Catch(DomainError) -export class DomainExceptionFilter implements ExceptionFilter { - catch(exception: DomainError, host: ArgumentsHost) { - const ctx = host.switchToHttp(); - const response = ctx.getResponse(); - - response.status(HttpStatus.BAD_REQUEST).json({ - success: false, - message: exception.message, - timestamp: new Date().toISOString(), - }); - } -} diff --git a/backend/services/identity-service/identity-service/src/shared/filters/global-exception.filter.ts b/backend/services/identity-service/identity-service/src/shared/filters/global-exception.filter.ts deleted file mode 100644 index 9972c226..00000000 --- a/backend/services/identity-service/identity-service/src/shared/filters/global-exception.filter.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { - ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus, - Injectable, NestInterceptor, ExecutionContext, CallHandler, -} from '@nestjs/common'; -import { Response } from 'express'; -import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; -import { DomainError, ApplicationError } from '@/shared/exceptions/domain.exception'; - -@Catch() -export class GlobalExceptionFilter implements ExceptionFilter { - catch(exception: unknown, host: ArgumentsHost) { - const ctx = host.switchToHttp(); - const response = ctx.getResponse(); - - let status = HttpStatus.INTERNAL_SERVER_ERROR; - let message = '服务器内部错误'; - let code: string | undefined; - - if (exception instanceof HttpException) { - status = exception.getStatus(); - const exceptionResponse = exception.getResponse(); - if (typeof exceptionResponse === 'object' && exceptionResponse !== null) { - message = (exceptionResponse as any).message || message; - code = (exceptionResponse as any).code; - } else { - message = exceptionResponse as string; - } - } else if (exception instanceof DomainError) { - status = HttpStatus.BAD_REQUEST; - message = exception.message; - } else if (exception instanceof ApplicationError) { - status = HttpStatus.BAD_REQUEST; - message = exception.message; - code = exception.code; - } else if (exception instanceof Error) { - message = exception.message; - } - - response.status(status).json({ - success: false, - code, - message, - timestamp: new Date().toISOString(), - }); - } -} - -export interface ApiResponse { - success: boolean; - data?: T; - message?: string; -} - -@Injectable() -export class TransformInterceptor implements NestInterceptor> { - intercept(context: ExecutionContext, next: CallHandler): Observable> { - return next.handle().pipe( - map((data) => ({ - success: true, - data, - })), - ); - } -} diff --git a/backend/services/identity-service/identity-service/src/shared/guards/jwt-auth.guard.ts b/backend/services/identity-service/identity-service/src/shared/guards/jwt-auth.guard.ts deleted file mode 100644 index 1dfafec9..00000000 --- a/backend/services/identity-service/identity-service/src/shared/guards/jwt-auth.guard.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { Injectable, CanActivate, ExecutionContext, createParamDecorator, SetMetadata } from '@nestjs/common'; -import { Reflector } from '@nestjs/core'; -import { JwtService } from '@nestjs/jwt'; -import { UnauthorizedException } from '@/shared/exceptions/domain.exception'; - -export interface JwtPayload { - userId: string; - accountSequence: number; - deviceId: string; - type: 'access' | 'refresh'; -} - -export interface CurrentUserData { - userId: string; - accountSequence: number; - deviceId: string; -} - -export const IS_PUBLIC_KEY = 'isPublic'; -export const Public = () => SetMetadata(IS_PUBLIC_KEY, true); - -export const CurrentUser = createParamDecorator( - (data: keyof CurrentUserData | undefined, ctx: ExecutionContext): CurrentUserData | string | number => { - const request = ctx.switchToHttp().getRequest(); - const user = request.user as CurrentUserData; - return data ? user?.[data] : user; - }, -); - -@Injectable() -export class JwtAuthGuard implements CanActivate { - constructor( - private readonly jwtService: JwtService, - private readonly reflector: Reflector, - ) {} - - async canActivate(context: ExecutionContext): Promise { - const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [ - context.getHandler(), - context.getClass(), - ]); - if (isPublic) return true; - - const request = context.switchToHttp().getRequest(); - const token = this.extractTokenFromHeader(request); - - if (!token) throw new UnauthorizedException('缺少认证令牌'); - - try { - const payload = await this.jwtService.verifyAsync(token); - if (payload.type !== 'access') throw new UnauthorizedException('无效的令牌类型'); - request.user = { - userId: payload.userId, - accountSequence: payload.accountSequence, - deviceId: payload.deviceId, - }; - } catch { - throw new UnauthorizedException('令牌无效或已过期'); - } - - return true; - } - - private extractTokenFromHeader(request: any): string | undefined { - const [type, token] = request.headers.authorization?.split(' ') ?? []; - return type === 'Bearer' ? token : undefined; - } -} diff --git a/backend/services/identity-service/identity-service/src/shared/interceptors/transform.interceptor.ts b/backend/services/identity-service/identity-service/src/shared/interceptors/transform.interceptor.ts deleted file mode 100644 index 8501f5aa..00000000 --- a/backend/services/identity-service/identity-service/src/shared/interceptors/transform.interceptor.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; -import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; - -export interface ApiResponse { - success: boolean; - data?: T; - message?: string; -} - -@Injectable() -export class TransformInterceptor implements NestInterceptor> { - intercept(context: ExecutionContext, next: CallHandler): Observable> { - return next.handle().pipe( - map((data) => ({ - success: true, - data, - })), - ); - } -} diff --git a/backend/services/identity-service/identity-service/tsconfig.json b/backend/services/identity-service/identity-service/tsconfig.json deleted file mode 100644 index bd3c3946..00000000 --- a/backend/services/identity-service/identity-service/tsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "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/*"] - } - } -} diff --git a/backend/services/identity-service/package.json b/backend/services/identity-service/package.json index e9173e39..fc9db1c0 100644 --- a/backend/services/identity-service/package.json +++ b/backend/services/identity-service/package.json @@ -5,6 +5,9 @@ "author": "RWA Team", "private": true, "license": "UNLICENSED", + "prisma": { + "schema": "src/infrastructure/persistence/prisma/schema.prisma" + }, "scripts": { "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",