This commit is contained in:
hailin 2025-11-24 08:27:36 +00:00
parent 1469b45723
commit ef1ef8ba25
5 changed files with 256 additions and 136 deletions

View File

@ -0,0 +1,253 @@
# Identity Service 部署和测试指南
## 项目架构验证
本项目采用DDD领域驱动设计四层架构
### 1. 表现层API Layer
- 位置:`src/api/`
- 职责处理HTTP请求参数验证响应格式化
- 主要组件Controllers, DTOs, Validators
### 2. 应用层Application Layer
- 位置:`src/application/`
- 职责:编排业务流程,事务管理,命令查询分离
- 主要组件Commands, Queries, Application Services
### 3. 领域层Domain Layer
- 位置:`src/domain/`
- 职责:业务逻辑,领域模型,业务规则
- 主要组件Aggregates, Entities, Value Objects, Domain Services, Events
### 4. 基础设施层Infrastructure Layer
- 位置:`src/infrastructure/`
- 职责:技术实现细节,外部服务集成
- 主要组件Repositories, Redis, Kafka, External Services
## 构建和部署
### 环境准备
1. **Node.js**: 需要 Node.js 20.x 或更高版本
2. **Docker**: 用于容器化部署
3. **PostgreSQL**: 数据库可通过Docker运行
4. **Redis**: 缓存服务可通过Docker运行
5. **Kafka**: 消息队列可通过Docker运行
### 本地开发
1. **克隆项目并安装依赖**
```bash
cd identity-service
npm install
```
2. **配置环境变量**
```bash
cp .env.example .env
# 编辑 .env 文件,配置数据库连接等信息
```
3. **启动依赖服务使用Docker**
```bash
docker-compose up -d postgres redis kafka
```
4. **初始化数据库**
```bash
# 生成Prisma Client
npm run prisma:generate
# 运行数据库迁移
npm run prisma:migrate
```
5. **启动服务**
```bash
# 开发模式(支持热重载)
npm run start:dev
# 生产模式
npm run build
npm run start:prod
```
### Docker部署
1. **使用Docker Compose一键部署**
```bash
# 构建并启动所有服务
docker-compose up -d
# 查看日志
docker-compose logs -f identity-service
# 停止服务
docker-compose down
```
2. **单独构建Docker镜像**
```bash
# 构建镜像
docker build -t identity-service:latest .
# 运行容器
docker run -d \
-p 3000:3000 \
-e DATABASE_URL="postgresql://user:pass@host:5432/db" \
-e REDIS_HOST="redis-host" \
-e KAFKA_BROKERS="kafka:9092" \
--name identity-service \
identity-service:latest
```
## API测试
### 1. 访问Swagger文档
启动服务后访问http://localhost:3000/api/docs
### 2. 主要API端点
#### 自动创建账户(首次使用)
```bash
curl -X POST http://localhost:3000/api/v1/user/auto-create \
-H "Content-Type: application/json" \
-d '{
"deviceId": "device-123-456",
"deviceName": "iPhone 15 Pro",
"inviterReferralCode": "ABCD1234",
"provinceCode": "GD",
"cityCode": "GZ"
}'
```
#### 用助记词恢复账户
```bash
curl -X POST http://localhost:3000/api/v1/user/recover-by-mnemonic \
-H "Content-Type: application/json" \
-d '{
"accountSequence": 1000001,
"mnemonic": "your twelve word mnemonic phrase here",
"newDeviceId": "device-789",
"deviceName": "New Device"
}'
```
#### 自动登录Token刷新
```bash
curl -X POST http://localhost:3000/api/v1/user/auto-login \
-H "Content-Type: application/json" \
-d '{
"refreshToken": "your-refresh-token",
"deviceId": "device-123-456"
}'
```
#### 获取个人资料(需要认证)
```bash
curl -X GET http://localhost:3000/api/v1/user/my-profile \
-H "Authorization: Bearer your-access-token"
```
#### 绑定手机号
```bash
# 1. 发送验证码
curl -X POST http://localhost:3000/api/v1/user/send-sms-code \
-H "Content-Type: application/json" \
-d '{
"phoneNumber": "+8613812345678",
"type": "BIND_PHONE"
}'
# 2. 绑定手机号
curl -X POST http://localhost:3000/api/v1/user/bind-phone \
-H "Authorization: Bearer your-access-token" \
-H "Content-Type: application/json" \
-d '{
"phoneNumber": "+8613812345678",
"smsCode": "123456"
}'
```
## 生产环境部署建议
### 1. 数据库优化
- 使用连接池
- 设置合适的索引
- 定期备份
### 2. Redis配置
- 启用持久化RDB或AOF
- 设置合适的内存限制
- 配置密码认证
### 3. Kafka配置
- 设置合适的分区数
- 配置副本因子
- 监控消费者滞后
### 4. 安全建议
- 使用强密码的JWT密钥
- 启用HTTPS
- 配置CORS白名单
- 使用环境变量管理敏感信息
### 5. 监控和日志
- 集成监控工具Prometheus、Grafana
- 配置结构化日志
- 设置告警规则
## 故障排查
### 常见问题
1. **数据库连接失败**
- 检查DATABASE_URL配置
- 确认PostgreSQL服务正在运行
- 检查网络连接
2. **Redis连接失败**
- 检查REDIS_HOST和REDIS_PORT
- 确认Redis服务正在运行
- 检查防火墙设置
3. **Kafka连接失败**
- 检查KAFKA_BROKERS配置
- 确认Kafka和Zookeeper正在运行
- 检查网络连接
4. **Prisma错误**
- 运行 `npm run prisma:generate`
- 检查数据库模式是否最新
- 运行 `npm run prisma:migrate`
### 日志查看
```bash
# Docker日志
docker-compose logs -f identity-service
# 本地开发日志
npm run start:dev
```
## 性能优化
1. **启用查询优化**
- 使用Prisma的`select`和`include`优化查询
- 实现分页
- 使用Redis缓存热数据
2. **并发处理**
- 使用NestJS的异步特性
- 合理配置线程池
- 使用消息队列处理耗时任务
3. **监控指标**
- API响应时间
- 数据库查询性能
- Redis命中率
- Kafka消费延迟
## 联系支持
如有问题,请查看项目文档或联系开发团队。

View File

@ -4,7 +4,7 @@ FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
COPY src/infrastructure/persistence/prisma ./src/infrastructure/persistence/prisma/
COPY prisma ./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/src/infrastructure/persistence/prisma ./src/infrastructure/persistence/prisma
COPY --from=builder /app/prisma ./prisma
COPY --from=builder /app/package*.json ./
ENV NODE_ENV=production

View File

@ -6,7 +6,7 @@
"private": true,
"license": "UNLICENSED",
"prisma": {
"schema": "src/infrastructure/persistence/prisma/schema.prisma"
"schema": "prisma/schema.prisma"
},
"scripts": {
"build": "nest build",

View File

@ -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")
}