This commit is contained in:
parent
1469b45723
commit
ef1ef8ba25
|
|
@ -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消费延迟
|
||||
|
||||
## 联系支持
|
||||
|
||||
如有问题,请查看项目文档或联系开发团队。
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
"private": true,
|
||||
"license": "UNLICENSED",
|
||||
"prisma": {
|
||||
"schema": "src/infrastructure/persistence/prisma/schema.prisma"
|
||||
"schema": "prisma/schema.prisma"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "nest build",
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
Loading…
Reference in New Issue