# Wallet Service 部署文档

## 概述

本文档描述 Wallet Service 的部署架构、配置方式、Docker 容器化以及生产环境部署流程。

---

## 部署架构

```
                         ┌─────────────────┐
                         │   Load Balancer │
                         │    (Nginx/ALB)  │
                         └────────┬────────┘
                                  │
         ┌────────────────────────┼────────────────────────┐
         │                        │                        │
         ▼                        ▼                        ▼
┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│ Wallet Service  │    │ Wallet Service  │    │ Wallet Service  │
│   Instance 1    │    │   Instance 2    │    │   Instance N    │
└────────┬────────┘    └────────┬────────┘    └────────┬────────┘
         │                      │                      │
         └──────────────────────┼──────────────────────┘
                                │
                       ┌────────┴────────┐
                       │   PostgreSQL    │
                       │   (Primary)     │
                       └────────┬────────┘
                                │
                       ┌────────┴────────┐
                       │   PostgreSQL    │
                       │   (Replica)     │
                       └─────────────────┘
```

---

## 环境要求

### 系统要求

| 组件 | 最低要求 | 推荐配置 |
|-----|---------|---------|
| CPU | 2 核 | 4+ 核 |
| 内存 | 2 GB | 4+ GB |
| 磁盘 | 20 GB SSD | 50+ GB SSD |
| Node.js | 20.x | 20.x LTS |
| PostgreSQL | 15.x | 15.x |

### 依赖服务

- PostgreSQL 15.x 数据库
- Docker (可选，用于容器化部署)
- Kubernetes (可选，用于编排)

---

## 环境变量配置

### 必需变量

| 变量 | 描述 | 示例 |
|-----|------|-----|
| `DATABASE_URL` | PostgreSQL 连接字符串 | `postgresql://user:pass@host:5432/db` |
| `JWT_SECRET` | JWT 签名密钥 | `your-secret-key-here` |
| `NODE_ENV` | 环境标识 | `production` |
| `PORT` | 服务端口 | `3000` |

### 可选变量

| 变量 | 描述 | 默认值 |
|-----|------|-------|
| `JWT_EXPIRES_IN` | JWT 过期时间 | `24h` |
| `LOG_LEVEL` | 日志级别 | `info` |
| `CORS_ORIGIN` | CORS 允许源 | `*` |

### 环境配置文件

```bash
# .env.production
DATABASE_URL="postgresql://wallet:strong_password@db.example.com:5432/wallet_prod?schema=public&connection_limit=10"
JWT_SECRET="your-production-secret-key-min-32-chars"
NODE_ENV=production
PORT=3000
LOG_LEVEL=info
```

---

## Docker 部署

### Dockerfile

```dockerfile
# Dockerfile
# 构建阶段
FROM node:20-alpine AS builder

WORKDIR /app

# 复制依赖文件
COPY package*.json ./
COPY prisma ./prisma/

# 安装依赖
RUN npm ci --only=production=false

# 复制源代码
COPY . .

# 生成 Prisma Client
RUN npx prisma generate

# 构建应用
RUN npm run build

# 清理开发依赖
RUN npm prune --production

# 生产阶段
FROM node:20-alpine AS production

WORKDIR /app

# 安装 dumb-init 用于信号处理
RUN apk add --no-cache dumb-init

# 创建非 root 用户
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nestjs -u 1001

# 复制构建产物
COPY --from=builder --chown=nestjs:nodejs /app/dist ./dist
COPY --from=builder --chown=nestjs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nestjs:nodejs /app/prisma ./prisma
COPY --from=builder --chown=nestjs:nodejs /app/package.json ./

# 切换到非 root 用户
USER nestjs

# 暴露端口
EXPOSE 3000

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/v1/health || exit 1

# 启动命令
ENTRYPOINT ["dumb-init", "--"]
CMD ["sh", "-c", "npx prisma migrate deploy && node dist/main"]
```

### .dockerignore

```
# .dockerignore
node_modules
dist
coverage
.git
.gitignore
*.md
*.log
.env*
!.env.example
test
.vscode
.idea
```

### 构建和运行

```bash
# 构建镜像
docker build -t wallet-service:latest .

# 运行容器
docker run -d \
  --name wallet-service \
  -p 3000:3000 \
  -e DATABASE_URL="postgresql://wallet:password@host:5432/wallet" \
  -e JWT_SECRET="your-secret-key" \
  -e NODE_ENV=production \
  wallet-service:latest

# 查看日志
docker logs -f wallet-service
```

---

## Docker Compose 部署

### docker-compose.yml

```yaml
# docker-compose.yml
version: '3.8'

services:
  wallet-service:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgresql://wallet:wallet123@postgres:5432/wallet?schema=public
      JWT_SECRET: ${JWT_SECRET:-development-secret-change-in-production}
      NODE_ENV: ${NODE_ENV:-production}
    depends_on:
      postgres:
        condition: service_healthy
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/api/v1/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  postgres:
    image: postgres:15-alpine
    environment:
      POSTGRES_USER: wallet
      POSTGRES_PASSWORD: wallet123
      POSTGRES_DB: wallet
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U wallet -d wallet"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  postgres_data:
```

### docker-compose.prod.yml

```yaml
# docker-compose.prod.yml
version: '3.8'

services:
  wallet-service:
    image: wallet-service:${VERSION:-latest}
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: ${DATABASE_URL}
      JWT_SECRET: ${JWT_SECRET}
      NODE_ENV: production
      LOG_LEVEL: info
    deploy:
      replicas: 3
      update_config:
        parallelism: 1
        delay: 10s
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
    healthcheck:
      test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/api/v1/health"]
      interval: 30s
      timeout: 10s
      retries: 3
```

### 运行命令

```bash
# 开发环境
docker-compose up -d

# 生产环境
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d

# 扩展副本
docker-compose -f docker-compose.prod.yml up -d --scale wallet-service=3
```

---

## Kubernetes 部署

### Deployment

```yaml
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wallet-service
  labels:
    app: wallet-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: wallet-service
  template:
    metadata:
      labels:
        app: wallet-service
    spec:
      containers:
        - name: wallet-service
          image: wallet-service:latest
          ports:
            - containerPort: 3000
          env:
            - name: NODE_ENV
              value: "production"
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: wallet-secrets
                  key: database-url
            - name: JWT_SECRET
              valueFrom:
                secretKeyRef:
                  name: wallet-secrets
                  key: jwt-secret
          resources:
            requests:
              memory: "256Mi"
              cpu: "250m"
            limits:
              memory: "512Mi"
              cpu: "500m"
          livenessProbe:
            httpGet:
              path: /api/v1/health
              port: 3000
            initialDelaySeconds: 30
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /api/v1/health
              port: 3000
            initialDelaySeconds: 5
            periodSeconds: 5
```

### Service

```yaml
# k8s/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: wallet-service
spec:
  type: ClusterIP
  ports:
    - port: 80
      targetPort: 3000
  selector:
    app: wallet-service
```

### Secret

```yaml
# k8s/secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: wallet-secrets
type: Opaque
stringData:
  database-url: "postgresql://wallet:password@postgres:5432/wallet"
  jwt-secret: "your-production-secret-key"
```

### Ingress

```yaml
# k8s/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: wallet-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
    - host: wallet.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: wallet-service
                port:
                  number: 80
```

### 部署命令

```bash
# 创建 Secret
kubectl apply -f k8s/secret.yaml

# 部署服务
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yaml
kubectl apply -f k8s/ingress.yaml

# 查看状态
kubectl get pods -l app=wallet-service
kubectl get svc wallet-service

# 滚动更新
kubectl set image deployment/wallet-service wallet-service=wallet-service:v2.0.0

# 回滚
kubectl rollout undo deployment/wallet-service
```

---

## 数据库迁移

### Prisma 迁移

```bash
# 开发环境 - 创建迁移
npx prisma migrate dev --name add_new_field

# 生产环境 - 应用迁移
npx prisma migrate deploy

# 查看迁移状态
npx prisma migrate status
```

### 迁移脚本

```bash
#!/bin/bash
# scripts/migrate.sh

set -e

echo "Running database migrations..."

# 等待数据库就绪
until pg_isready -h $DB_HOST -p $DB_PORT -U $DB_USER; do
  echo "Waiting for database..."
  sleep 2
done

# 运行迁移
npx prisma migrate deploy

echo "Migrations completed successfully!"
```

---

## CI/CD 流水线

### GitHub Actions

```yaml
# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]
  release:
    types: [published]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:15-alpine
        env:
          POSTGRES_USER: wallet
          POSTGRES_PASSWORD: wallet123
          POSTGRES_DB: wallet_test
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Generate Prisma Client
        run: npx prisma generate

      - name: Run migrations
        run: npx prisma db push
        env:
          DATABASE_URL: postgresql://wallet:wallet123@localhost:5432/wallet_test

      - name: Run tests
        run: npm test
        env:
          DATABASE_URL: postgresql://wallet:wallet123@localhost:5432/wallet_test
          JWT_SECRET: test-secret

      - name: Run E2E tests
        run: npm run test:e2e
        env:
          DATABASE_URL: postgresql://wallet:wallet123@localhost:5432/wallet_test
          JWT_SECRET: test-secret

  build:
    needs: test
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v3

      - name: Log in to Container Registry
        uses: docker/login-action@v2
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v4
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch
            type=semver,pattern={{version}}
            type=sha

      - name: Build and push
        uses: docker/build-push-action@v4
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

  deploy:
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'

    steps:
      - name: Deploy to production
        run: |
          # 触发部署 (例如 ArgoCD, Kubernetes, 或 SSH)
          echo "Deploying to production..."
```

---

## 监控和日志

### 健康检查端点

```bash
# 健康检查
curl http://localhost:3000/api/v1/health

# 响应示例
{
  "success": true,
  "data": {
    "status": "ok",
    "service": "wallet-service",
    "timestamp": "2024-01-01T00:00:00.000Z"
  }
}
```

### 日志配置

```typescript
// src/main.ts
import { Logger } from '@nestjs/common';

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    logger: process.env.NODE_ENV === 'production'
      ? ['error', 'warn', 'log']
      : ['error', 'warn', 'log', 'debug', 'verbose'],
  });
  // ...
}
```

### 日志格式 (生产环境)

```json
{
  "timestamp": "2024-01-01T00:00:00.000Z",
  "level": "info",
  "context": "WalletController",
  "message": "Deposit processed",
  "userId": "12345",
  "amount": 100,
  "requestId": "abc-123"
}
```

---

## 安全配置

### Nginx 反向代理

```nginx
# nginx.conf
upstream wallet_service {
    server wallet-service-1:3000;
    server wallet-service-2:3000;
    server wallet-service-3:3000;
}

server {
    listen 80;
    server_name wallet.example.com;

    # 重定向到 HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name wallet.example.com;

    ssl_certificate /etc/nginx/ssl/cert.pem;
    ssl_certificate_key /etc/nginx/ssl/key.pem;

    # 安全头
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Strict-Transport-Security "max-age=31536000" always;

    # 速率限制
    limit_req zone=api burst=20 nodelay;

    location /api/ {
        proxy_pass http://wallet_service;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
    }
}
```

### 环境变量安全

```bash
# 不要在版本控制中提交敏感信息
# 使用环境变量或密钥管理服务

# AWS Secrets Manager
aws secretsmanager get-secret-value --secret-id wallet/production

# HashiCorp Vault
vault kv get secret/wallet/production
```

---

## 故障排除

### 常见问题

#### 1. 数据库连接失败

```bash
# 检查连接
psql $DATABASE_URL -c "SELECT 1"

# 检查网络
nc -zv db.example.com 5432
```

#### 2. 容器无法启动

```bash
# 查看日志
docker logs wallet-service

# 进入容器调试
docker exec -it wallet-service sh
```

#### 3. 迁移失败

```bash
# 查看迁移状态
npx prisma migrate status

# 重置数据库 (开发环境)
npx prisma migrate reset
```

### 回滚流程

```bash
# Docker 回滚
docker stop wallet-service
docker run -d --name wallet-service wallet-service:previous-tag

# Kubernetes 回滚
kubectl rollout undo deployment/wallet-service

# 数据库回滚 (手动)
# 需要提前备份，Prisma 不支持自动回滚
pg_restore -d wallet wallet_backup.dump
```

---

## 部署检查清单

### 部署前

- [ ] 所有测试通过
- [ ] 代码审查完成
- [ ] 环境变量配置正确
- [ ] 数据库备份完成
- [ ] 迁移脚本测试通过

### 部署中

- [ ] 监控指标正常
- [ ] 健康检查通过
- [ ] 日志无错误
- [ ] API 响应正常

### 部署后

- [ ] 功能验证通过
- [ ] 性能测试通过
- [ ] 安全扫描通过
- [ ] 文档更新

---

## 联系方式

如遇部署问题，请联系：

- 运维团队: devops@example.com
- 开发团队: dev@example.com
- 紧急联系: oncall@example.com

