React 18.x 学习计划 - 第十三天:部署与DevOps实践

学习目标

  • 掌握生产环境构建和优化
  • 学会Docker容器化部署
  • 理解CI/CD流程实现
  • 掌握监控和日志系统
  • 理解安全最佳实践
  • 构建完整的部署方案

学习时间安排

总时长:8-9小时

  • 生产环境构建:1.5小时
  • Docker容器化:2小时
  • CI/CD流程:2小时
  • 监控和日志:1.5小时
  • 安全实践:1小时
  • 完整部署实践:2-3小时

第一部分:生产环境构建 (1.5小时)

1.1 生产环境优化配置

Webpack生产配置(详细注释版)
javascript 复制代码
// webpack.prod.js
// 导入webpack
const webpack = require('webpack');
// 导入路径处理
const path = require('path');
// 导入插件
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');

// 导出生产环境配置
module.exports = {
  // 生产模式
  mode: 'production',
  
  // 入口文件
  entry: {
    main: './src/index.js',
    // 可以添加多个入口点进行代码分割
  },
  
  // 输出配置
  output: {
    // 输出目录
    path: path.resolve(__dirname, 'build'),
    // 输出文件名,使用内容哈希实现缓存
    filename: 'static/js/[name].[contenthash:8].js',
    // 代码分割后的文件名
    chunkFilename: 'static/js/[name].[contenthash:8].chunk.js',
    // 资源文件名
    assetModuleFilename: 'static/media/[name].[hash:8][ext]',
    // 清理输出目录
    clean: true,
    // 公共路径
    publicPath: '/',
  },
  
  // 模块解析配置
  resolve: {
    // 文件扩展名
    extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
    // 别名配置
    alias: {
      '@': path.resolve(__dirname, 'src'),
      'components': path.resolve(__dirname, 'src/components'),
      'utils': path.resolve(__dirname, 'src/utils'),
      'store': path.resolve(__dirname, 'src/store'),
    },
  },
  
  // 模块处理规则
  module: {
    rules: [
      // JavaScript/TypeScript文件处理
      {
        test: /\.(js|jsx|ts|tsx)$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            // 缓存配置,提升构建速度
            cacheDirectory: true,
            cacheCompression: false,
            // Babel配置
            presets: [
              ['@babel/preset-env', {
                useBuiltIns: 'entry',
                corejs: 3,
              }],
              ['@babel/preset-react', {
                runtime: 'automatic',
              }],
              '@babel/preset-typescript',
            ],
            plugins: [
              // 生产环境移除console和debugger
              ['transform-remove-console', { exclude: ['error', 'warn'] }],
            ],
          },
        },
      },
      // CSS文件处理
      {
        test: /\.css$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: {
                // CSS模块化配置
                localIdentName: '[name]__[local]--[hash:base64:5]',
              },
            },
          },
          'postcss-loader',
        ],
      },
      // 图片资源处理
      {
        test: /\.(png|jpe?g|gif|svg|webp)$/i,
        type: 'asset',
        parser: {
          dataUrlCondition: {
            // 小于8KB的图片转为base64
            maxSize: 8 * 1024,
          },
        },
      },
      // 字体文件处理
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        type: 'asset/resource',
      },
    ],
  },
  
  // 优化配置
  optimization: {
    // 最小化配置
    minimize: true,
    // 最小化工具
    minimizer: [
      // JavaScript压缩
      new TerserPlugin({
        terserOptions: {
          compress: {
            // 移除console
            drop_console: true,
            // 移除debugger
            drop_debugger: true,
            // 移除未使用的代码
            pure_funcs: ['console.log'],
          },
          format: {
            // 移除注释
            comments: false,
          },
        },
        extractComments: false,
      }),
      // CSS压缩
      new CssMinimizerPlugin(),
    ],
    // 代码分割配置
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        // 提取vendor代码
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          priority: 10,
          reuseExistingChunk: true,
        },
        // 提取公共代码
        common: {
          minChunks: 2,
          priority: 5,
          reuseExistingChunk: true,
        },
        // 提取React相关代码
        react: {
          test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
          name: 'react',
          priority: 20,
          reuseExistingChunk: true,
        },
      },
    },
    // 运行时chunk
    runtimeChunk: {
      name: 'runtime',
    },
  },
  
  // 插件配置
  plugins: [
    // 定义环境变量
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('production'),
      'process.env.API_URL': JSON.stringify(process.env.API_URL || 'https://api.example.com'),
    }),
    // Gzip压缩
    new CompressionPlugin({
      algorithm: 'gzip',
      test: /\.(js|css|html|svg)$/,
      threshold: 8192,
      minRatio: 0.8,
    }),
    // Bundle分析(可选)
    process.env.ANALYZE && new BundleAnalyzerPlugin(),
  ].filter(Boolean),
  
  // 性能提示
  performance: {
    // 关闭性能提示
    hints: false,
    // 或者设置最大资源大小
    // maxEntrypointSize: 512000,
    // maxAssetSize: 512000,
  },
  
  // Source Map配置(生产环境使用source-map)
  devtool: 'source-map',
};
Vite生产配置(详细注释版)
javascript 复制代码
// vite.config.js
// 导入vite
import { defineConfig } from 'vite';
// 导入React插件
import react from '@vitejs/plugin-react';
// 导入路径处理
import path from 'path';

// 导出Vite配置
export default defineConfig({
  // 插件配置
  plugins: [
    react({
      // React快速刷新
      fastRefresh: true,
      // Babel配置
      babel: {
        plugins: [
          // 生产环境移除console
          process.env.NODE_ENV === 'production' && [
            'transform-remove-console',
            { exclude: ['error', 'warn'] }
          ],
        ].filter(Boolean),
      },
    }),
  ],
  
  // 路径别名
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src'),
      'components': path.resolve(__dirname, 'src/components'),
      'utils': path.resolve(__dirname, 'src/utils'),
      'store': path.resolve(__dirname, 'src/store'),
    },
  },
  
  // 构建配置
  build: {
    // 输出目录
    outDir: 'dist',
    // 资源目录
    assetsDir: 'static',
    // 源代码映射
    sourcemap: true,
    // 最小化
    minify: 'terser',
    // Terser选项
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true,
      },
    },
    // 代码分割
    rollupOptions: {
      output: {
        // 手动代码分割
        manualChunks: {
          // React相关
          'react-vendor': ['react', 'react-dom', 'react-router-dom'],
          // Redux相关
          'redux-vendor': ['@reduxjs/toolkit', 'react-redux'],
          // 工具库
          'utils-vendor': ['lodash', 'axios', 'dayjs'],
        },
        // 文件命名
        chunkFileNames: 'static/js/[name]-[hash].js',
        entryFileNames: 'static/js/[name]-[hash].js',
        assetFileNames: 'static/[ext]/[name]-[hash].[ext]',
      },
    },
    // 块大小警告限制
    chunkSizeWarningLimit: 1000,
  },
  
  // 服务器配置(开发环境)
  server: {
    port: 3000,
    open: true,
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''),
      },
    },
  },
  
  // 环境变量配置
  envPrefix: 'VITE_',
});

1.2 环境变量配置

环境变量文件(详细注释版)
bash 复制代码
# .env.development
# 开发环境变量
REACT_APP_API_URL=http://localhost:8080/api
REACT_APP_ENV=development
REACT_APP_DEBUG=true
REACT_APP_VERSION=1.0.0

# .env.production
# 生产环境变量
REACT_APP_API_URL=https://api.example.com
REACT_APP_ENV=production
REACT_APP_DEBUG=false
REACT_APP_VERSION=1.0.0

# .env.staging
# 预发布环境变量
REACT_APP_API_URL=https://staging-api.example.com
REACT_APP_ENV=staging
REACT_APP_DEBUG=true
REACT_APP_VERSION=1.0.0
环境变量使用(详细注释版)
typescript 复制代码
// src/config/env.ts
// 环境变量配置类型
interface EnvConfig {
  apiUrl: string;
  env: string;
  debug: boolean;
  version: string;
}

// 获取环境变量配置
function getEnvConfig(): EnvConfig {
  return {
    // API地址
    apiUrl: process.env.REACT_APP_API_URL || 'http://localhost:8080/api',
    // 环境类型
    env: process.env.REACT_APP_ENV || 'development',
    // 是否开启调试
    debug: process.env.REACT_APP_DEBUG === 'true',
    // 版本号
    version: process.env.REACT_APP_VERSION || '1.0.0',
  };
}

// 导出环境配置
export const envConfig = getEnvConfig();

// 导出环境判断函数
export const isDevelopment = () => envConfig.env === 'development';
export const isProduction = () => envConfig.env === 'production';
export const isStaging = () => envConfig.env === 'staging';

第二部分:Docker容器化 (2小时)

2.1 Dockerfile配置

多阶段构建Dockerfile(详细注释版)
dockerfile 复制代码
# Dockerfile
# 第一阶段:构建阶段
FROM node:18-alpine AS builder

# 设置工作目录
WORKDIR /app

# 复制package文件
COPY package*.json ./

# 安装依赖(使用npm ci提升安装速度和可靠性)
RUN npm ci --only=production=false

# 复制源代码
COPY . .

# 构建应用
RUN npm run build

# 第二阶段:生产阶段
FROM nginx:alpine AS production

# 安装curl用于健康检查
RUN apk add --no-cache curl

# 复制构建产物到nginx目录
COPY --from=builder /app/build /usr/share/nginx/html

# 复制nginx配置文件
COPY nginx.conf /etc/nginx/conf.d/default.conf

# 暴露端口
EXPOSE 80

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost/ || exit 1

# 启动nginx
CMD ["nginx", "-g", "daemon off;"]
Nginx配置文件(详细注释版)
nginx 复制代码
# nginx.conf
# 上游服务器配置(如果需要代理API)
upstream api_server {
    server backend:8080;
}

# HTTP服务器配置
server {
    # 监听端口
    listen 80;
    
    # 服务器名称
    server_name localhost;
    
    # 根目录
    root /usr/share/nginx/html;
    
    # 索引文件
    index index.html;
    
    # 字符集
    charset utf-8;
    
    # 日志配置
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;
    
    # Gzip压缩配置
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css text/xml text/javascript 
               application/json application/javascript application/xml+rss 
               application/rss+xml font/truetype font/opentype 
               application/vnd.ms-fontobject image/svg+xml;
    
    # 静态资源缓存配置
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        access_log off;
    }
    
    # API代理配置
    location /api {
        proxy_pass http://api_server;
        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;
    }
    
    # React路由配置(所有路由都返回index.html)
    location / {
        try_files $uri $uri/ /index.html;
    }
    
    # 安全头配置
    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 Referrer-Policy "no-referrer-when-downgrade" always;
    
    # 禁止访问隐藏文件
    location ~ /\. {
        deny all;
        access_log off;
        log_not_found off;
    }
}

2.2 Docker Compose配置

Docker Compose文件(详细注释版)
yaml 复制代码
# docker-compose.yml
version: '3.8'

# 服务定义
services:
  # 前端服务
  frontend:
    # 构建配置
    build:
      # 构建上下文
      context: .
      # Dockerfile路径
      dockerfile: Dockerfile
      # 构建参数
      args:
        - NODE_ENV=production
    # 容器名称
    container_name: react-app
    # 端口映射
    ports:
      - "80:80"
    # 环境变量
    environment:
      - REACT_APP_API_URL=http://localhost:8080/api
    # 依赖服务
    depends_on:
      - backend
    # 重启策略
    restart: unless-stopped
    # 网络配置
    networks:
      - app-network
    # 健康检查
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  # 后端服务(示例)
  backend:
    image: node:18-alpine
    container_name: backend-api
    working_dir: /app
    volumes:
      - ./backend:/app
    ports:
      - "8080:8080"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://user:password@db:5432/mydb
    depends_on:
      - db
    networks:
      - app-network
    restart: unless-stopped

  # 数据库服务(示例)
  db:
    image: postgres:15-alpine
    container_name: postgres-db
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=password
      - POSTGRES_DB=mydb
    volumes:
      - postgres-data:/var/lib/postgresql/data
    networks:
      - app-network
    restart: unless-stopped

# 网络配置
networks:
  app-network:
    driver: bridge

# 数据卷配置
volumes:
  postgres-data:

2.3 Docker构建和部署脚本

构建脚本(详细注释版)
bash 复制代码
#!/bin/bash
# build.sh
# Docker构建和部署脚本

# 设置错误时退出
set -e

# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

# 打印信息函数
print_info() {
    echo -e "${GREEN}[INFO]${NC} $1"
}

print_warn() {
    echo -e "${YELLOW}[WARN]${NC} $1"
}

print_error() {
    echo -e "${RED}[ERROR]${NC} $1"
}

# 检查Docker是否安装
check_docker() {
    if ! command -v docker &> /dev/null; then
        print_error "Docker is not installed. Please install Docker first."
        exit 1
    fi
    print_info "Docker is installed"
}

# 检查Docker Compose是否安装
check_docker_compose() {
    if ! command -v docker-compose &> /dev/null; then
        print_error "Docker Compose is not installed. Please install Docker Compose first."
        exit 1
    fi
    print_info "Docker Compose is installed"
}

# 构建Docker镜像
build_image() {
    print_info "Building Docker image..."
    docker build -t react-app:latest .
    print_info "Docker image built successfully"
}

# 运行容器
run_container() {
    print_info "Starting containers..."
    docker-compose up -d
    print_info "Containers started successfully"
}

# 停止容器
stop_container() {
    print_info "Stopping containers..."
    docker-compose down
    print_info "Containers stopped successfully"
}

# 查看日志
view_logs() {
    print_info "Viewing logs..."
    docker-compose logs -f
}

# 清理未使用的资源
cleanup() {
    print_info "Cleaning up unused resources..."
    docker system prune -f
    print_info "Cleanup completed"
}

# 主函数
main() {
    case "$1" in
        build)
            check_docker
            build_image
            ;;
        start)
            check_docker
            check_docker_compose
            run_container
            ;;
        stop)
            check_docker_compose
            stop_container
            ;;
        logs)
            check_docker_compose
            view_logs
            ;;
        cleanup)
            check_docker
            cleanup
            ;;
        *)
            echo "Usage: $0 {build|start|stop|logs|cleanup}"
            exit 1
            ;;
    esac
}

# 执行主函数
main "$@"

第三部分:CI/CD流程 (2小时)

3.1 GitHub Actions配置

CI/CD工作流(详细注释版)
yaml 复制代码
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline

# 触发条件
on:
  # 推送到主分支时触发
  push:
    branches:
      - main
      - develop
  # 创建Pull Request时触发
  pull_request:
    branches:
      - main
      - develop
  # 手动触发
  workflow_dispatch:

# 环境变量
env:
  NODE_VERSION: '18'
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

# 工作流任务
jobs:
  # 代码检查和测试
  lint-and-test:
    name: Lint and Test
    runs-on: ubuntu-latest
    
    steps:
      # 检出代码
      - name: Checkout code
        uses: actions/checkout@v3
      
      # 设置Node.js
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
      
      # 安装依赖
      - name: Install dependencies
        run: npm ci
      
      # 运行ESLint
      - name: Run ESLint
        run: npm run lint
      
      # 运行类型检查
      - name: Run TypeScript check
        run: npm run type-check
      
      # 运行测试
      - name: Run tests
        run: npm run test:coverage
      
      # 上传测试覆盖率
      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage/lcov.info

  # 构建应用
  build:
    name: Build Application
    runs-on: ubuntu-latest
    needs: lint-and-test
    
    steps:
      # 检出代码
      - name: Checkout code
        uses: actions/checkout@v3
      
      # 设置Node.js
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
      
      # 安装依赖
      - name: Install dependencies
        run: npm ci
      
      # 构建应用
      - name: Build application
        run: npm run build
        env:
          REACT_APP_API_URL: ${{ secrets.API_URL }}
          REACT_APP_ENV: production
      
      # 上传构建产物
      - name: Upload build artifacts
        uses: actions/upload-artifact@v3
        with:
          name: build-files
          path: build/
          retention-days: 7

  # 构建Docker镜像
  build-docker:
    name: Build Docker Image
    runs-on: ubuntu-latest
    needs: build
    if: github.ref == 'refs/heads/main'
    
    steps:
      # 检出代码
      - name: Checkout code
        uses: actions/checkout@v3
      
      # 登录到容器注册表
      - name: Login to Container Registry
        uses: docker/login-action@v2
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      
      # 设置Docker Buildx
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2
      
      # 构建并推送Docker镜像
      - name: Build and push Docker image
        uses: docker/build-push-action@v4
        with:
          context: .
          push: true
          tags: |
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
          cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache
          cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max

  # 部署到生产环境
  deploy:
    name: Deploy to Production
    runs-on: ubuntu-latest
    needs: build-docker
    if: github.ref == 'refs/heads/main'
    environment:
      name: production
      url: https://example.com
    
    steps:
      # 检出代码
      - name: Checkout code
        uses: actions/checkout@v3
      
      # 部署到服务器
      - name: Deploy to server
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.SSH_KEY }}
          script: |
            cd /opt/app
            docker-compose pull
            docker-compose up -d
            docker system prune -f
      
      # 健康检查
      - name: Health check
        run: |
          sleep 10
          curl -f https://example.com/health || exit 1

3.2 GitLab CI配置

GitLab CI配置文件(详细注释版)
yaml 复制代码
# .gitlab-ci.yml
# GitLab CI/CD配置文件

# 定义阶段
stages:
  - lint
  - test
  - build
  - deploy

# 定义变量
variables:
  NODE_VERSION: "18"
  DOCKER_IMAGE: $CI_REGISTRY_IMAGE
  DOCKER_TAG: $CI_COMMIT_REF_SLUG

# 缓存配置
cache:
  paths:
    - node_modules/
    - .npm/

# Lint阶段
lint:
  stage: lint
  image: node:$NODE_VERSION
  script:
    - npm ci
    - npm run lint
    - npm run type-check
  only:
    - merge_requests
    - main
    - develop

# 测试阶段
test:
  stage: test
  image: node:$NODE_VERSION
  script:
    - npm ci
    - npm run test:coverage
  coverage: '/Lines\s*:\s*(\d+\.\d+)%/'
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml
    paths:
      - coverage/
    expire_in: 1 week
  only:
    - merge_requests
    - main
    - develop

# 构建阶段
build:
  stage: build
  image: node:$NODE_VERSION
  script:
    - npm ci
    - npm run build
  artifacts:
    paths:
      - build/
    expire_in: 1 week
  only:
    - main
    - develop

# Docker构建阶段
docker-build:
  stage: build
  image: docker:latest
  services:
    - docker:dind
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
  script:
    - docker build -t $DOCKER_IMAGE:$DOCKER_TAG .
    - docker push $DOCKER_IMAGE:$DOCKER_TAG
  only:
    - main

# 部署到生产环境
deploy-production:
  stage: deploy
  image: alpine:latest
  before_script:
    - apk add --no-cache openssh-client
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - ssh-keyscan $DEPLOY_HOST >> ~/.ssh/known_hosts
  script:
    - ssh $DEPLOY_USER@$DEPLOY_HOST "cd /opt/app && docker-compose pull && docker-compose up -d"
  environment:
    name: production
    url: https://example.com
  only:
    - main
  when: manual

第四部分:监控和日志 (1.5小时)

4.1 应用监控

性能监控Hook(详细注释版)
typescript 复制代码
// src/hooks/usePerformanceMonitor.ts
// 导入React
import { useEffect, useRef } from 'react';

// 性能指标接口
interface PerformanceMetrics {
  // 首次内容绘制时间
  fcp: number | null;
  // 最大内容绘制时间
  lcp: number | null;
  // 首次输入延迟
  fid: number | null;
  // 累积布局偏移
  cls: number | null;
  // 总阻塞时间
  tbt: number | null;
}

// 性能监控Hook
export function usePerformanceMonitor() {
  // 使用useRef保存指标
  const metricsRef = useRef<PerformanceMetrics>({
    fcp: null,
    lcp: null,
    fid: null,
    cls: null,
    tbt: null,
  });

  useEffect(() => {
    // 检查Performance Observer API支持
    if (typeof window === 'undefined' || !('PerformanceObserver' in window)) {
      return;
    }

    // 观察性能指标
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        // 处理不同类型的性能指标
        if (entry.entryType === 'paint') {
          const paintEntry = entry as PerformancePaintTiming;
          if (paintEntry.name === 'first-contentful-paint') {
            metricsRef.current.fcp = paintEntry.startTime;
            // 发送到监控服务
            sendMetric('fcp', paintEntry.startTime);
          }
        }

        if (entry.entryType === 'largest-contentful-paint') {
          const lcpEntry = entry as PerformanceEntry & {
            renderTime?: number;
            loadTime?: number;
          };
          metricsRef.current.lcp = lcpEntry.renderTime || lcpEntry.loadTime || null;
          if (metricsRef.current.lcp) {
            sendMetric('lcp', metricsRef.current.lcp);
          }
        }

        if (entry.entryType === 'first-input') {
          const fidEntry = entry as PerformanceEventTiming;
          metricsRef.current.fid = fidEntry.processingStart - fidEntry.startTime;
          if (metricsRef.current.fid) {
            sendMetric('fid', metricsRef.current.fid);
          }
        }

        if (entry.entryType === 'layout-shift') {
          const clsEntry = entry as PerformanceEntry & { value?: number };
          if (!clsEntry.hadRecentInput && clsEntry.value) {
            metricsRef.current.cls = (metricsRef.current.cls || 0) + clsEntry.value;
            sendMetric('cls', metricsRef.current.cls);
          }
        }
      }
    });

    // 观察不同类型的性能指标
    try {
      observer.observe({ entryTypes: ['paint', 'largest-contentful-paint', 'first-input', 'layout-shift'] });
    } catch (e) {
      console.warn('Performance Observer not fully supported:', e);
    }

    // 清理函数
    return () => {
      observer.disconnect();
    };
  }, []);

  // 发送指标到监控服务
  const sendMetric = (name: string, value: number) => {
    // 在实际应用中,这里会发送到监控服务(如Google Analytics、Sentry等)
    if (process.env.NODE_ENV === 'production') {
      // 发送到监控服务
      // analytics.track('performance_metric', { name, value });
      console.log(`Performance metric: ${name} = ${value}ms`);
    }
  };

  return metricsRef.current;
}

4.2 错误日志系统

日志服务(详细注释版)
typescript 复制代码
// src/services/loggingService.ts
// 日志级别枚举
export enum LogLevel {
  DEBUG = 'DEBUG',
  INFO = 'INFO',
  WARN = 'WARN',
  ERROR = 'ERROR',
}

// 日志接口
interface LogEntry {
  // 日志级别
  level: LogLevel;
  // 日志消息
  message: string;
  // 时间戳
  timestamp: string;
  // 额外数据
  data?: any;
  // 用户信息
  user?: {
    id: string;
    email?: string;
  };
  // 环境信息
  environment?: string;
  // 错误堆栈
  stack?: string;
}

// 日志服务类
class LoggingService {
  private logs: LogEntry[] = [];
  private maxLogs = 100;
  private flushInterval = 60000; // 1分钟

  constructor() {
    // 定期刷新日志
    setInterval(() => {
      this.flush();
    }, this.flushInterval);

    // 页面卸载时刷新日志
    window.addEventListener('beforeunload', () => {
      this.flush();
    });
  }

  // 记录日志
  private log(level: LogLevel, message: string, data?: any) {
    const entry: LogEntry = {
      level,
      message,
      timestamp: new Date().toISOString(),
      data,
      environment: process.env.NODE_ENV,
    };

    // 添加到日志数组
    this.logs.push(entry);

    // 限制日志数量
    if (this.logs.length > this.maxLogs) {
      this.logs.shift();
    }

    // 控制台输出
    this.consoleLog(level, message, data);

    // 如果是错误级别,立即发送
    if (level === LogLevel.ERROR) {
      this.sendLog(entry);
    }
  }

  // 控制台输出
  private consoleLog(level: LogLevel, message: string, data?: any) {
    const styles = {
      [LogLevel.DEBUG]: 'color: gray',
      [LogLevel.INFO]: 'color: blue',
      [LogLevel.WARN]: 'color: orange',
      [LogLevel.ERROR]: 'color: red',
    };

    console.log(`%c[${level}] ${message}`, styles[level], data || '');
  }

  // 发送日志到服务器
  private async sendLog(entry: LogEntry) {
    try {
      // 在实际应用中,这里会发送到日志服务器
      // await fetch('/api/logs', {
      //   method: 'POST',
      //   headers: { 'Content-Type': 'application/json' },
      //   body: JSON.stringify(entry),
      // });
      console.log('Log sent:', entry);
    } catch (error) {
      console.error('Failed to send log:', error);
    }
  }

  // 刷新所有日志
  async flush() {
    if (this.logs.length === 0) {
      return;
    }

    const logsToSend = [...this.logs];
    this.logs = [];

    try {
      // 批量发送日志
      // await fetch('/api/logs/batch', {
      //   method: 'POST',
      //   headers: { 'Content-Type': 'application/json' },
      //   body: JSON.stringify(logsToSend),
      // });
      console.log('Logs flushed:', logsToSend.length);
    } catch (error) {
      console.error('Failed to flush logs:', error);
      // 如果发送失败,重新添加到日志数组
      this.logs.unshift(...logsToSend);
    }
  }

  // 公共方法
  debug(message: string, data?: any) {
    this.log(LogLevel.DEBUG, message, data);
  }

  info(message: string, data?: any) {
    this.log(LogLevel.INFO, message, data);
  }

  warn(message: string, data?: any) {
    this.log(LogLevel.WARN, message, data);
  }

  error(message: string, error?: Error | any) {
    const entry: LogEntry = {
      level: LogLevel.ERROR,
      message: error?.message || message,
      timestamp: new Date().toISOString(),
      data: error,
      stack: error?.stack,
      environment: process.env.NODE_ENV,
    };

    this.logs.push(entry);
    this.consoleLog(LogLevel.ERROR, message, error);
    this.sendLog(entry);
  }
}

// 创建全局日志服务实例
const loggingService = new LoggingService();

// 导出日志服务
export default loggingService;

第五部分:安全实践 (1小时)

5.1 安全配置

安全中间件(详细注释版)
typescript 复制代码
// src/utils/security.ts
// 安全工具函数

// XSS防护:转义HTML
export function escapeHtml(text: string): string {
  const map: Record<string, string> = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#039;',
  };
  return text.replace(/[&<>"']/g, (m) => map[m]);
}

// CSRF Token生成
export function generateCSRFToken(): string {
  const array = new Uint8Array(32);
  crypto.getRandomValues(array);
  return Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join('');
}

// 验证CSRF Token
export function validateCSRFToken(token: string, storedToken: string): boolean {
  return token === storedToken;
}

// 内容安全策略配置
export const cspConfig = {
  'default-src': ["'self'"],
  'script-src': ["'self'", "'unsafe-inline'", "'unsafe-eval'"],
  'style-src': ["'self'", "'unsafe-inline'"],
  'img-src': ["'self'", 'data:', 'https:'],
  'font-src': ["'self'", 'data:'],
  'connect-src': ["'self'", 'https://api.example.com'],
  'frame-ancestors': ["'none'"],
  'base-uri': ["'self'"],
  'form-action': ["'self'"],
};

// 生成CSP头
export function generateCSPHeader(): string {
  return Object.entries(cspConfig)
    .map(([directive, sources]) => `${directive} ${sources.join(' ')}`)
    .join('; ');
}
安全Headers配置(详细注释版)
typescript 复制代码
// src/config/securityHeaders.ts
// 安全头配置

export const securityHeaders = {
  // 防止点击劫持
  'X-Frame-Options': 'DENY',
  // 防止MIME类型嗅探
  'X-Content-Type-Options': 'nosniff',
  // XSS保护
  'X-XSS-Protection': '1; mode=block',
  // 引用策略
  'Referrer-Policy': 'strict-origin-when-cross-origin',
  // 权限策略
  'Permissions-Policy': 'geolocation=(), microphone=(), camera=()',
  // 严格传输安全(HTTPS)
  'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
};

第六部分:完整部署实践 (2-3小时)

部署检查清单

部署前检查(详细注释版)
typescript 复制代码
// scripts/pre-deploy-check.ts
// 部署前检查脚本

import { execSync } from 'child_process';
import fs from 'fs';
import path from 'path';

// 检查项接口
interface CheckItem {
  name: string;
  check: () => boolean;
  message: string;
}

// 检查函数数组
const checks: CheckItem[] = [
  {
    name: '环境变量检查',
    check: () => {
      const requiredEnvVars = [
        'REACT_APP_API_URL',
        'REACT_APP_ENV',
      ];
      return requiredEnvVars.every(envVar => process.env[envVar]);
    },
    message: '所有必需的环境变量都已设置',
  },
  {
    name: '构建文件检查',
    check: () => {
      const buildDir = path.join(process.cwd(), 'build');
      return fs.existsSync(buildDir) && fs.statSync(buildDir).isDirectory();
    },
    message: '构建目录存在',
  },
  {
    name: 'index.html检查',
    check: () => {
      const indexPath = path.join(process.cwd(), 'build', 'index.html');
      return fs.existsSync(indexPath);
    },
    message: 'index.html文件存在',
  },
  {
    name: '静态资源检查',
    check: () => {
      const staticDir = path.join(process.cwd(), 'build', 'static');
      return fs.existsSync(staticDir);
    },
    message: '静态资源目录存在',
  },
  {
    name: 'Dockerfile检查',
    check: () => {
      const dockerfilePath = path.join(process.cwd(), 'Dockerfile');
      return fs.existsSync(dockerfilePath);
    },
    message: 'Dockerfile存在',
  },
  {
    name: 'Nginx配置检查',
    check: () => {
      const nginxConfigPath = path.join(process.cwd(), 'nginx.conf');
      return fs.existsSync(nginxConfigPath);
    },
    message: 'Nginx配置文件存在',
  },
];

// 运行检查
function runChecks() {
  console.log('开始部署前检查...\n');
  
  let allPassed = true;
  
  checks.forEach((check, index) => {
    const passed = check.check();
    const status = passed ? '✓' : '✗';
    const statusColor = passed ? '\x1b[32m' : '\x1b[31m';
    
    console.log(`${statusColor}${status}\x1b[0m [${index + 1}/${checks.length}] ${check.name}`);
    console.log(`   ${check.message}\n`);
    
    if (!passed) {
      allPassed = false;
    }
  });
  
  if (allPassed) {
    console.log('\x1b[32m所有检查通过!可以开始部署。\x1b[0m\n');
    process.exit(0);
  } else {
    console.log('\x1b[31m部分检查未通过,请修复后重试。\x1b[0m\n');
    process.exit(1);
  }
}

// 执行检查
runChecks();
部署脚本(详细注释版)
bash 复制代码
#!/bin/bash
# deploy.sh
# 完整部署脚本

set -e

# 配置
ENVIRONMENT=${1:-production}
VERSION=${2:-latest}
DEPLOY_PATH="/opt/app"

echo "开始部署到 $ENVIRONMENT 环境..."

# 1. 运行部署前检查
echo "运行部署前检查..."
npm run pre-deploy-check

# 2. 构建应用
echo "构建应用..."
npm run build

# 3. 构建Docker镜像
echo "构建Docker镜像..."
docker build -t react-app:$VERSION .

# 4. 推送到镜像仓库
echo "推送镜像到仓库..."
docker tag react-app:$VERSION registry.example.com/react-app:$VERSION
docker push registry.example.com/react-app:$VERSION

# 5. 部署到服务器
echo "部署到服务器..."
ssh user@server << EOF
  cd $DEPLOY_PATH
  docker pull registry.example.com/react-app:$VERSION
  docker-compose down
  docker-compose up -d
  docker system prune -f
EOF

# 6. 健康检查
echo "执行健康检查..."
sleep 10
curl -f https://example.com/health || exit 1

echo "部署完成!"

练习题目

基础练习

  1. 构建配置练习
bash 复制代码
# 练习1:配置生产环境构建
# 实现:Webpack/Vite生产配置、代码分割、资源优化
# 包含:压缩、Tree Shaking、代码分割

# 练习2:配置环境变量
# 实现:多环境配置、环境变量管理
# 包含:开发、测试、生产环境配置
  1. Docker练习
bash 复制代码
# 练习3:创建Dockerfile
# 实现:多阶段构建、优化镜像大小
# 包含:构建阶段、生产阶段、健康检查

# 练习4:配置Docker Compose
# 实现:多服务编排、网络配置
# 包含:前端、后端、数据库服务

进阶练习

  1. CI/CD练习
bash 复制代码
# 练习5:配置GitHub Actions
# 实现:自动化测试、构建、部署
# 包含:代码检查、测试、Docker构建、部署

# 练习6:实现完整部署流程
# 实现:自动化部署、回滚机制
# 包含:部署脚本、健康检查、监控
相关推荐
wdfk_prog2 小时前
[Linux]学习笔记系列 -- [drivers][dma]dmapool
linux·笔记·学习
电饭叔2 小时前
Tkinter Button 括号内的核心参数详解
python·学习
摘星编程3 小时前
OpenHarmony环境下React Native:DatePicker日期选择器
react native·react.js·harmonyos
橙露3 小时前
NNG通信框架:现代分布式系统的通信解决方案与应用场景深度分析
运维·网络·tcp/ip·react.js·架构
摘星编程3 小时前
用React Native开发OpenHarmony应用:Calendar日期范围选择
javascript·react native·react.js
闵帆4 小时前
反演学习器面临的鸿沟
人工智能·学习·机器学习
EnglishJun4 小时前
数据结构的学习(二)---Makefile的使用
linux·运维·学习
呱呱巨基4 小时前
c语言 文件操作
c语言·开发语言·c++·笔记·学习
嗯嗯**6 小时前
Neo4j学习1:概述、安装
学习·neo4j·概述·安装·图数据库·jdk21