Files
jiebanke/docs/性能优化文档.md

60 KiB
Raw Blame History

解班客项目性能优化文档

📋 文档概述

本文档详细说明解班客项目的性能优化策略、监控方案和优化实践,涵盖前端、后端、数据库和基础设施的全方位性能优化。

文档目标

  • 建立完整的性能监控体系
  • 提供系统性的性能优化方案
  • 制定性能基准和优化目标
  • 建立性能问题诊断和解决流程

🎯 性能目标和指标

核心性能指标

前端性能指标

// 前端性能目标
const FRONTEND_PERFORMANCE_TARGETS = {
  // 页面加载性能
  pageLoad: {
    FCP: 1.5, // 首次内容绘制 < 1.5s
    LCP: 2.5, // 最大内容绘制 < 2.5s
    FID: 100, // 首次输入延迟 < 100ms
    CLS: 0.1, // 累积布局偏移 < 0.1
    TTI: 3.5  // 可交互时间 < 3.5s
  },
  
  // 资源加载
  resources: {
    jsBundle: 250, // JS包大小 < 250KB
    cssBundle: 50,  // CSS包大小 < 50KB
    images: 100,    // 图片大小 < 100KB
    fonts: 30       // 字体大小 < 30KB
  },
  
  // 运行时性能
  runtime: {
    fps: 60,        // 帧率 >= 60fps
    memoryUsage: 50, // 内存使用 < 50MB
    cpuUsage: 30    // CPU使用率 < 30%
  }
}

// 后端性能指标
const BACKEND_PERFORMANCE_TARGETS = {
  // API响应时间
  api: {
    p50: 200,  // 50%请求 < 200ms
    p95: 500,  // 95%请求 < 500ms
    p99: 1000  // 99%请求 < 1000ms
  },
  
  // 吞吐量
  throughput: {
    rps: 1000,     // 每秒请求数 >= 1000
    concurrent: 500 // 并发用户数 >= 500
  },
  
  // 资源使用
  resources: {
    cpu: 70,    // CPU使用率 < 70%
    memory: 80, // 内存使用率 < 80%
    disk: 85    // 磁盘使用率 < 85%
  }
}

// 数据库性能指标
const DATABASE_PERFORMANCE_TARGETS = {
  // 查询性能
  query: {
    select: 50,  // SELECT查询 < 50ms
    insert: 100, // INSERT操作 < 100ms
    update: 150, // UPDATE操作 < 150ms
    delete: 100  // DELETE操作 < 100ms
  },
  
  // 连接池
  connection: {
    poolSize: 20,     // 连接池大小
    maxWait: 5000,    // 最大等待时间 < 5s
    activeRatio: 0.8  // 活跃连接比例 < 80%
  }
}

性能监控仪表板

// 性能监控配置
class PerformanceMonitor {
  constructor() {
    this.metrics = new Map()
    this.alerts = new Map()
    this.collectors = []
  }
  
  // 初始化监控
  initialize() {
    // 前端性能监控
    this.initWebVitalsMonitoring()
    
    // 后端性能监控
    this.initServerMonitoring()
    
    // 数据库性能监控
    this.initDatabaseMonitoring()
    
    // 基础设施监控
    this.initInfrastructureMonitoring()
  }
  
  // Web Vitals监控
  initWebVitalsMonitoring() {
    // 使用Web Vitals库
    import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
      getCLS(this.onCLS.bind(this))
      getFID(this.onFID.bind(this))
      getFCP(this.onFCP.bind(this))
      getLCP(this.onLCP.bind(this))
      getTTFB(this.onTTFB.bind(this))
    })
    
    // 自定义性能指标
    this.monitorCustomMetrics()
  }
  
  // 处理CLS指标
  onCLS(metric) {
    this.recordMetric('CLS', metric.value)
    
    if (metric.value > FRONTEND_PERFORMANCE_TARGETS.pageLoad.CLS) {
      this.triggerAlert('CLS_HIGH', {
        value: metric.value,
        threshold: FRONTEND_PERFORMANCE_TARGETS.pageLoad.CLS,
        url: window.location.href
      })
    }
  }
  
  // 处理FID指标
  onFID(metric) {
    this.recordMetric('FID', metric.value)
    
    if (metric.value > FRONTEND_PERFORMANCE_TARGETS.pageLoad.FID) {
      this.triggerAlert('FID_HIGH', {
        value: metric.value,
        threshold: FRONTEND_PERFORMANCE_TARGETS.pageLoad.FID,
        url: window.location.href
      })
    }
  }
  
  // 自定义性能监控
  monitorCustomMetrics() {
    // 监控资源加载时间
    this.monitorResourceTiming()
    
    // 监控API请求性能
    this.monitorAPIPerformance()
    
    // 监控内存使用
    this.monitorMemoryUsage()
    
    // 监控帧率
    this.monitorFrameRate()
  }
  
  // 资源加载时间监控
  monitorResourceTiming() {
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        if (entry.entryType === 'resource') {
          const loadTime = entry.responseEnd - entry.startTime
          
          this.recordMetric('resource_load_time', {
            name: entry.name,
            type: this.getResourceType(entry.name),
            loadTime: loadTime,
            size: entry.transferSize
          })
          
          // 检查是否超过阈值
          if (this.isResourceLoadTimeSlow(entry.name, loadTime)) {
            this.triggerAlert('SLOW_RESOURCE', {
              resource: entry.name,
              loadTime: loadTime
            })
          }
        }
      }
    })
    
    observer.observe({ entryTypes: ['resource'] })
  }
  
  // API性能监控
  monitorAPIPerformance() {
    const originalFetch = window.fetch
    
    window.fetch = async (...args) => {
      const startTime = performance.now()
      const url = args[0]
      
      try {
        const response = await originalFetch(...args)
        const endTime = performance.now()
        const duration = endTime - startTime
        
        this.recordMetric('api_request', {
          url: url,
          method: args[1]?.method || 'GET',
          status: response.status,
          duration: duration,
          success: response.ok
        })
        
        // 检查API响应时间
        if (duration > 1000) { // 超过1秒
          this.triggerAlert('SLOW_API', {
            url: url,
            duration: duration,
            status: response.status
          })
        }
        
        return response
      } catch (error) {
        const endTime = performance.now()
        const duration = endTime - startTime
        
        this.recordMetric('api_request', {
          url: url,
          method: args[1]?.method || 'GET',
          duration: duration,
          success: false,
          error: error.message
        })
        
        this.triggerAlert('API_ERROR', {
          url: url,
          error: error.message,
          duration: duration
        })
        
        throw error
      }
    }
  }
  
  // 内存使用监控
  monitorMemoryUsage() {
    if ('memory' in performance) {
      setInterval(() => {
        const memory = performance.memory
        
        this.recordMetric('memory_usage', {
          used: memory.usedJSHeapSize,
          total: memory.totalJSHeapSize,
          limit: memory.jsHeapSizeLimit,
          usage_ratio: memory.usedJSHeapSize / memory.jsHeapSizeLimit
        })
        
        // 检查内存使用率
        const usageRatio = memory.usedJSHeapSize / memory.jsHeapSizeLimit
        if (usageRatio > 0.8) { // 超过80%
          this.triggerAlert('HIGH_MEMORY_USAGE', {
            usage: memory.usedJSHeapSize,
            limit: memory.jsHeapSizeLimit,
            ratio: usageRatio
          })
        }
      }, 30000) // 每30秒检查一次
    }
  }
  
  // 帧率监控
  monitorFrameRate() {
    let lastTime = performance.now()
    let frameCount = 0
    
    const measureFPS = () => {
      frameCount++
      const currentTime = performance.now()
      
      if (currentTime - lastTime >= 1000) { // 每秒计算一次
        const fps = Math.round((frameCount * 1000) / (currentTime - lastTime))
        
        this.recordMetric('fps', fps)
        
        if (fps < 30) { // 低于30fps
          this.triggerAlert('LOW_FPS', { fps: fps })
        }
        
        frameCount = 0
        lastTime = currentTime
      }
      
      requestAnimationFrame(measureFPS)
    }
    
    requestAnimationFrame(measureFPS)
  }
  
  // 记录指标
  recordMetric(name, value) {
    const timestamp = Date.now()
    
    if (!this.metrics.has(name)) {
      this.metrics.set(name, [])
    }
    
    this.metrics.get(name).push({
      timestamp: timestamp,
      value: value
    })
    
    // 保持最近1000条记录
    const records = this.metrics.get(name)
    if (records.length > 1000) {
      records.splice(0, records.length - 1000)
    }
    
    // 发送到监控服务
    this.sendToMonitoringService(name, value, timestamp)
  }
  
  // 触发告警
  triggerAlert(type, data) {
    const alert = {
      type: type,
      data: data,
      timestamp: Date.now(),
      url: window.location.href,
      userAgent: navigator.userAgent
    }
    
    console.warn('Performance Alert:', alert)
    
    // 发送告警到监控服务
    this.sendAlertToService(alert)
  }
  
  // 发送数据到监控服务
  async sendToMonitoringService(metric, value, timestamp) {
    try {
      await fetch('/api/monitoring/metrics', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          metric: metric,
          value: value,
          timestamp: timestamp,
          url: window.location.href,
          session_id: this.getSessionId()
        })
      })
    } catch (error) {
      console.error('Failed to send metric to monitoring service:', error)
    }
  }
  
  // 发送告警到服务
  async sendAlertToService(alert) {
    try {
      await fetch('/api/monitoring/alerts', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(alert)
      })
    } catch (error) {
      console.error('Failed to send alert to monitoring service:', error)
    }
  }
  
  // 获取会话ID
  getSessionId() {
    let sessionId = sessionStorage.getItem('performance_session_id')
    if (!sessionId) {
      sessionId = 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9)
      sessionStorage.setItem('performance_session_id', sessionId)
    }
    return sessionId
  }
}

// 初始化性能监控
const performanceMonitor = new PerformanceMonitor()
performanceMonitor.initialize()

🚀 前端性能优化

代码分割和懒加载

Vue路由懒加载

// router/index.js - 路由懒加载配置
import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import(/* webpackChunkName: "home" */ '@/views/Home.vue')
  },
  {
    path: '/animals',
    name: 'Animals',
    component: () => import(/* webpackChunkName: "animals" */ '@/views/Animals.vue')
  },
  {
    path: '/adopt',
    name: 'Adopt',
    component: () => import(/* webpackChunkName: "adopt" */ '@/views/Adopt.vue')
  },
  {
    path: '/profile',
    name: 'Profile',
    component: () => import(/* webpackChunkName: "profile" */ '@/views/Profile.vue'),
    meta: { requiresAuth: true }
  },
  {
    path: '/admin',
    name: 'Admin',
    component: () => import(/* webpackChunkName: "admin" */ '@/views/admin/AdminLayout.vue'),
    children: [
      {
        path: 'dashboard',
        component: () => import(/* webpackChunkName: "admin-dashboard" */ '@/views/admin/Dashboard.vue')
      },
      {
        path: 'users',
        component: () => import(/* webpackChunkName: "admin-users" */ '@/views/admin/Users.vue')
      },
      {
        path: 'animals',
        component: () => import(/* webpackChunkName: "admin-animals" */ '@/views/admin/Animals.vue')
      }
    ]
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router

组件懒加载

<!-- AnimalList.vue - 组件懒加载示例 -->
<template>
  <div class="animal-list">
    <div class="filters">
      <!-- 过滤器组件 -->
      <AnimalFilters @filter="handleFilter" />
    </div>
    
    <div class="list-container">
      <!-- 虚拟滚动列表 -->
      <VirtualList
        :items="filteredAnimals"
        :item-height="200"
        :container-height="600"
        @scroll-end="loadMore"
      >
        <template #item="{ item }">
          <!-- 懒加载动物卡片组件 -->
          <Suspense>
            <template #default>
              <AnimalCard :animal="item" />
            </template>
            <template #fallback>
              <AnimalCardSkeleton />
            </template>
          </Suspense>
        </template>
      </VirtualList>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, defineAsyncComponent } from 'vue'
import { useAnimalStore } from '@/stores/animal'

// 异步组件
const AnimalFilters = defineAsyncComponent(() => import('@/components/AnimalFilters.vue'))
const AnimalCard = defineAsyncComponent(() => import('@/components/AnimalCard.vue'))
const AnimalCardSkeleton = defineAsyncComponent(() => import('@/components/AnimalCardSkeleton.vue'))
const VirtualList = defineAsyncComponent(() => import('@/components/VirtualList.vue'))

const animalStore = useAnimalStore()

const filteredAnimals = computed(() => animalStore.filteredAnimals)

// 处理过滤
const handleFilter = (filters) => {
  animalStore.applyFilters(filters)
}

// 加载更多
const loadMore = () => {
  animalStore.loadMoreAnimals()
}
</script>

虚拟滚动组件

<!-- VirtualList.vue - 虚拟滚动实现 -->
<template>
  <div 
    class="virtual-list"
    :style="{ height: containerHeight + 'px' }"
    @scroll="handleScroll"
    ref="containerRef"
  >
    <div 
      class="virtual-list-phantom"
      :style="{ height: totalHeight + 'px' }"
    ></div>
    
    <div 
      class="virtual-list-content"
      :style="{ transform: `translateY(${offsetY}px)` }"
    >
      <div
        v-for="item in visibleItems"
        :key="item.id"
        class="virtual-list-item"
        :style="{ height: itemHeight + 'px' }"
      >
        <slot name="item" :item="item" :index="item.index"></slot>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'

const props = defineProps({
  items: {
    type: Array,
    required: true
  },
  itemHeight: {
    type: Number,
    required: true
  },
  containerHeight: {
    type: Number,
    required: true
  },
  buffer: {
    type: Number,
    default: 5
  }
})

const emit = defineEmits(['scroll-end'])

const containerRef = ref(null)
const scrollTop = ref(0)

// 计算总高度
const totalHeight = computed(() => props.items.length * props.itemHeight)

// 计算可见区域的起始和结束索引
const visibleRange = computed(() => {
  const start = Math.floor(scrollTop.value / props.itemHeight)
  const end = Math.min(
    start + Math.ceil(props.containerHeight / props.itemHeight) + props.buffer,
    props.items.length
  )
  
  return {
    start: Math.max(0, start - props.buffer),
    end
  }
})

// 计算可见项目
const visibleItems = computed(() => {
  const { start, end } = visibleRange.value
  return props.items.slice(start, end).map((item, index) => ({
    ...item,
    index: start + index
  }))
})

// 计算偏移量
const offsetY = computed(() => {
  return visibleRange.value.start * props.itemHeight
})

// 处理滚动
const handleScroll = (event) => {
  scrollTop.value = event.target.scrollTop
  
  // 检查是否滚动到底部
  const { scrollTop: currentScrollTop, scrollHeight, clientHeight } = event.target
  
  if (currentScrollTop + clientHeight >= scrollHeight - 100) {
    emit('scroll-end')
  }
}

// 节流函数
const throttle = (func, delay) => {
  let timeoutId
  let lastExecTime = 0
  
  return function (...args) {
    const currentTime = Date.now()
    
    if (currentTime - lastExecTime > delay) {
      func.apply(this, args)
      lastExecTime = currentTime
    } else {
      clearTimeout(timeoutId)
      timeoutId = setTimeout(() => {
        func.apply(this, args)
        lastExecTime = Date.now()
      }, delay - (currentTime - lastExecTime))
    }
  }
}

// 使用节流的滚动处理
const throttledHandleScroll = throttle(handleScroll, 16) // 60fps

onMounted(() => {
  if (containerRef.value) {
    containerRef.value.addEventListener('scroll', throttledHandleScroll, { passive: true })
  }
})

onUnmounted(() => {
  if (containerRef.value) {
    containerRef.value.removeEventListener('scroll', throttledHandleScroll)
  }
})
</script>

<style scoped>
.virtual-list {
  position: relative;
  overflow-y: auto;
}

.virtual-list-phantom {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: -1;
}

.virtual-list-content {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
}

.virtual-list-item {
  box-sizing: border-box;
}
</style>

图片优化和懒加载

图片懒加载指令

// directives/lazyload.js - 图片懒加载指令
export const lazyload = {
  mounted(el, binding) {
    const { value: src, modifiers } = binding
    
    // 创建占位符
    const placeholder = modifiers.placeholder ? 
      '/images/placeholder.svg' : 
      'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZGRkIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxNCIgZmlsbD0iIzk5OSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPkxvYWRpbmcuLi48L3RleHQ+PC9zdmc+'
    
    el.src = placeholder
    
    // 创建Intersection Observer
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          const img = entry.target
          
          // 预加载图片
          const imageLoader = new Image()
          
          imageLoader.onload = () => {
            // 添加淡入效果
            img.style.transition = 'opacity 0.3s'
            img.style.opacity = '0'
            
            img.src = src
            
            // 图片加载完成后淡入
            img.onload = () => {
              img.style.opacity = '1'
              img.classList.add('loaded')
            }
          }
          
          imageLoader.onerror = () => {
            // 加载失败时显示错误图片
            img.src = '/images/error.svg'
            img.classList.add('error')
          }
          
          imageLoader.src = src
          
          // 停止观察
          observer.unobserve(img)
        }
      })
    }, {
      rootMargin: '50px' // 提前50px开始加载
    })
    
    observer.observe(el)
    
    // 保存observer引用以便清理
    el._lazyloadObserver = observer
  },
  
  unmounted(el) {
    if (el._lazyloadObserver) {
      el._lazyloadObserver.disconnect()
    }
  }
}

// 图片优化组件
// components/OptimizedImage.vue
<template>
  <div class="optimized-image" :class="{ loading: isLoading, error: hasError }">
    <img
      ref="imageRef"
      :src="currentSrc"
      :alt="alt"
      :loading="lazy ? 'lazy' : 'eager'"
      @load="handleLoad"
      @error="handleError"
      :style="imageStyle"
    />
    
    <div v-if="isLoading" class="loading-placeholder">
      <div class="loading-spinner"></div>
    </div>
    
    <div v-if="hasError" class="error-placeholder">
      <svg viewBox="0 0 24 24" class="error-icon">
        <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
      </svg>
      <span>图片加载失败</span>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, onMounted } from 'vue'

const props = defineProps({
  src: {
    type: String,
    required: true
  },
  alt: {
    type: String,
    default: ''
  },
  width: {
    type: [Number, String],
    default: 'auto'
  },
  height: {
    type: [Number, String],
    default: 'auto'
  },
  lazy: {
    type: Boolean,
    default: true
  },
  quality: {
    type: Number,
    default: 80,
    validator: (value) => value >= 1 && value <= 100
  },
  format: {
    type: String,
    default: 'webp',
    validator: (value) => ['webp', 'jpeg', 'png'].includes(value)
  },
  sizes: {
    type: String,
    default: '100vw'
  }
})

const imageRef = ref(null)
const isLoading = ref(true)
const hasError = ref(false)

// 生成优化后的图片URL
const optimizedSrc = computed(() => {
  if (!props.src) return ''
  
  // 如果是外部链接,直接返回
  if (props.src.startsWith('http')) {
    return props.src
  }
  
  // 构建优化参数
  const params = new URLSearchParams({
    q: props.quality,
    f: props.format
  })
  
  if (props.width !== 'auto') {
    params.append('w', props.width)
  }
  
  if (props.height !== 'auto') {
    params.append('h', props.height)
  }
  
  return `/api/images/optimize?src=${encodeURIComponent(props.src)}&${params.toString()}`
})

// 当前显示的图片源
const currentSrc = computed(() => {
  if (hasError.value) {
    return '/images/error.svg'
  }
  
  if (isLoading.value && props.lazy) {
    return '/images/placeholder.svg'
  }
  
  return optimizedSrc.value
})

// 图片样式
const imageStyle = computed(() => ({
  width: typeof props.width === 'number' ? `${props.width}px` : props.width,
  height: typeof props.height === 'number' ? `${props.height}px` : props.height,
  objectFit: 'cover'
}))

// 处理图片加载完成
const handleLoad = () => {
  isLoading.value = false
  hasError.value = false
}

// 处理图片加载错误
const handleError = () => {
  isLoading.value = false
  hasError.value = true
}

// 支持WebP检测
const supportsWebP = () => {
  const canvas = document.createElement('canvas')
  canvas.width = 1
  canvas.height = 1
  return canvas.toDataURL('image/webp').indexOf('data:image/webp') === 0
}

onMounted(() => {
  // 如果浏览器不支持WebP回退到JPEG
  if (props.format === 'webp' && !supportsWebP()) {
    // 这里可以修改format但由于是props需要通过其他方式处理
    console.warn('Browser does not support WebP, falling back to JPEG')
  }
})
</script>

<style scoped>
.optimized-image {
  position: relative;
  display: inline-block;
  overflow: hidden;
}

.optimized-image img {
  display: block;
  transition: opacity 0.3s ease;
}

.loading-placeholder,
.error-placeholder {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: #f5f5f5;
}

.loading-spinner {
  width: 24px;
  height: 24px;
  border: 2px solid #e0e0e0;
  border-top: 2px solid #1976d2;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

.error-placeholder {
  flex-direction: column;
  color: #666;
  font-size: 14px;
}

.error-icon {
  width: 24px;
  height: 24px;
  fill: #f44336;
  margin-bottom: 8px;
}

.optimized-image.loading img {
  opacity: 0.5;
}

.optimized-image.error img {
  opacity: 0.3;
}
</style>

缓存策略优化

Service Worker缓存

// public/sw.js - Service Worker缓存策略
const CACHE_NAME = 'jiebanke-v1.0.0'
const STATIC_CACHE = 'jiebanke-static-v1.0.0'
const DYNAMIC_CACHE = 'jiebanke-dynamic-v1.0.0'
const IMAGE_CACHE = 'jiebanke-images-v1.0.0'

// 需要缓存的静态资源
const STATIC_ASSETS = [
  '/',
  '/manifest.json',
  '/css/app.css',
  '/js/app.js',
  '/images/logo.svg',
  '/images/placeholder.svg',
  '/images/error.svg',
  '/fonts/roboto-regular.woff2',
  '/fonts/roboto-medium.woff2'
]

// 缓存策略配置
const CACHE_STRATEGIES = {
  // 静态资源:缓存优先
  static: {
    pattern: /\.(css|js|woff2?|svg|png|jpg|jpeg|gif|ico)$/,
    strategy: 'cacheFirst',
    maxAge: 30 * 24 * 60 * 60 * 1000, // 30天
    maxEntries: 100
  },
  
  // API请求网络优先
  api: {
    pattern: /^https?:\/\/.*\/api\//,
    strategy: 'networkFirst',
    maxAge: 5 * 60 * 1000, // 5分钟
    maxEntries: 50
  },
  
  // 图片:缓存优先
  images: {
    pattern: /\.(png|jpg|jpeg|gif|webp|svg)$/,
    strategy: 'cacheFirst',
    maxAge: 7 * 24 * 60 * 60 * 1000, // 7天
    maxEntries: 200
  },
  
  // HTML页面网络优先
  pages: {
    pattern: /\.html$|\/$/,
    strategy: 'networkFirst',
    maxAge: 24 * 60 * 60 * 1000, // 1天
    maxEntries: 20
  }
}

// 安装事件
self.addEventListener('install', (event) => {
  console.log('Service Worker installing...')
  
  event.waitUntil(
    caches.open(STATIC_CACHE)
      .then((cache) => {
        console.log('Caching static assets')
        return cache.addAll(STATIC_ASSETS)
      })
      .then(() => {
        console.log('Static assets cached')
        return self.skipWaiting()
      })
  )
})

// 激活事件
self.addEventListener('activate', (event) => {
  console.log('Service Worker activating...')
  
  event.waitUntil(
    caches.keys()
      .then((cacheNames) => {
        return Promise.all(
          cacheNames.map((cacheName) => {
            // 删除旧版本缓存
            if (cacheName !== STATIC_CACHE && 
                cacheName !== DYNAMIC_CACHE && 
                cacheName !== IMAGE_CACHE) {
              console.log('Deleting old cache:', cacheName)
              return caches.delete(cacheName)
            }
          })
        )
      })
      .then(() => {
        console.log('Service Worker activated')
        return self.clients.claim()
      })
  )
})

// 拦截请求
self.addEventListener('fetch', (event) => {
  const { request } = event
  const url = new URL(request.url)
  
  // 只处理同源请求
  if (url.origin !== location.origin) {
    return
  }
  
  // 根据请求类型选择缓存策略
  const strategy = getStrategy(request)
  
  if (strategy) {
    event.respondWith(handleRequest(request, strategy))
  }
})

// 获取缓存策略
function getStrategy(request) {
  const url = request.url
  
  for (const [name, config] of Object.entries(CACHE_STRATEGIES)) {
    if (config.pattern.test(url)) {
      return { name, ...config }
    }
  }
  
  return null
}

// 处理请求
async function handleRequest(request, strategy) {
  switch (strategy.strategy) {
    case 'cacheFirst':
      return cacheFirst(request, strategy)
    case 'networkFirst':
      return networkFirst(request, strategy)
    case 'staleWhileRevalidate':
      return staleWhileRevalidate(request, strategy)
    default:
      return fetch(request)
  }
}

// 缓存优先策略
async function cacheFirst(request, strategy) {
  const cacheName = getCacheName(strategy.name)
  const cache = await caches.open(cacheName)
  const cachedResponse = await cache.match(request)
  
  if (cachedResponse) {
    // 检查缓存是否过期
    const cacheTime = await getCacheTime(cache, request)
    if (cacheTime && Date.now() - cacheTime < strategy.maxAge) {
      return cachedResponse
    }
  }
  
  try {
    const networkResponse = await fetch(request)
    
    if (networkResponse.ok) {
      // 缓存新响应
      await cache.put(request, networkResponse.clone())
      await setCacheTime(cache, request, Date.now())
      
      // 清理过期缓存
      await cleanupCache(cache, strategy.maxEntries)
    }
    
    return networkResponse
  } catch (error) {
    // 网络失败时返回缓存
    if (cachedResponse) {
      return cachedResponse
    }
    throw error
  }
}

// 网络优先策略
async function networkFirst(request, strategy) {
  const cacheName = getCacheName(strategy.name)
  const cache = await caches.open(cacheName)
  
  try {
    const networkResponse = await fetch(request)
    
    if (networkResponse.ok) {
      // 缓存响应
      await cache.put(request, networkResponse.clone())
      await setCacheTime(cache, request, Date.now())
      
      // 清理过期缓存
      await cleanupCache(cache, strategy.maxEntries)
    }
    
    return networkResponse
  } catch (error) {
    // 网络失败时尝试从缓存获取
    const cachedResponse = await cache.match(request)
    
    if (cachedResponse) {
      const cacheTime = await getCacheTime(cache, request)
      if (!cacheTime || Date.now() - cacheTime < strategy.maxAge) {
        return cachedResponse
      }
    }
    
    throw error
  }
}

// 过期重新验证策略
async function staleWhileRevalidate(request, strategy) {
  const cacheName = getCacheName(strategy.name)
  const cache = await caches.open(cacheName)
  const cachedResponse = await cache.match(request)
  
  // 后台更新缓存
  const fetchPromise = fetch(request).then(async (networkResponse) => {
    if (networkResponse.ok) {
      await cache.put(request, networkResponse.clone())
      await setCacheTime(cache, request, Date.now())
      await cleanupCache(cache, strategy.maxEntries)
    }
    return networkResponse
  })
  
  // 如果有缓存,立即返回;否则等待网络响应
  return cachedResponse || fetchPromise
}

// 获取缓存名称
function getCacheName(strategyName) {
  switch (strategyName) {
    case 'static':
      return STATIC_CACHE
    case 'images':
      return IMAGE_CACHE
    default:
      return DYNAMIC_CACHE
  }
}

// 设置缓存时间
async function setCacheTime(cache, request, time) {
  const timeKey = `${request.url}:timestamp`
  await cache.put(timeKey, new Response(time.toString()))
}

// 获取缓存时间
async function getCacheTime(cache, request) {
  const timeKey = `${request.url}:timestamp`
  const timeResponse = await cache.match(timeKey)
  
  if (timeResponse) {
    const timeText = await timeResponse.text()
    return parseInt(timeText, 10)
  }
  
  return null
}

// 清理过期缓存
async function cleanupCache(cache, maxEntries) {
  const keys = await cache.keys()
  
  // 过滤出非时间戳的键
  const contentKeys = keys.filter(key => !key.url.includes(':timestamp'))
  
  if (contentKeys.length > maxEntries) {
    // 删除最旧的条目
    const keysToDelete = contentKeys.slice(0, contentKeys.length - maxEntries)
    
    for (const key of keysToDelete) {
      await cache.delete(key)
      // 同时删除对应的时间戳
      await cache.delete(`${key.url}:timestamp`)
    }
  }
}

// 消息处理
self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'SKIP_WAITING') {
    self.skipWaiting()
  }
})

🔧 后端性能优化

数据库查询优化

查询优化策略

// models/Animal.js - 动物模型优化
const { Model, DataTypes, Op } = require('sequelize')
const sequelize = require('../config/database')

class Animal extends Model {
  // 优化的查询方法
  static async findWithPagination(options = {}) {
    const {
      page = 1,
      limit = 20,
      filters = {},
      sort = 'created_at',
      order = 'DESC',
      include = []
    } = options
    
    const offset = (page - 1) * limit
    
    // 构建查询条件
    const where = this.buildWhereClause(filters)
    
    // 优化的查询配置
    const queryOptions = {
      where,
      limit: parseInt(limit),
      offset: parseInt(offset),
      order: [[sort, order]],
      include: this.buildIncludeClause(include),
      // 使用索引提示
      attributes: {
        include: [
          // 计算字段
          [
            sequelize.literal(`(
              SELECT COUNT(*)
              FROM adoptions
              WHERE adoptions.animal_id = Animal.id
              AND adoptions.status = 'completed'
            )`),
            'adoption_count'
          ]
        ]
      },
      // 子查询优化
      subQuery: false,
      // 启用查询缓存
      benchmark: true,
      logging: process.env.NODE_ENV === 'development'
    }
    
    // 执行查询
    const result = await this.findAndCountAll(queryOptions)
    
    return {
      data: result.rows,
      pagination: {
        page: parseInt(page),
        limit: parseInt(limit),
        total: result.count,
        pages: Math.ceil(result.count / limit)
      }
    }
  }
  
  // 构建WHERE子句
  static buildWhereClause(filters) {
    const where = {}
    
    // 状态过滤
    if (filters.status) {
      where.status = filters.status
    }
    
    // 类型过滤
    if (filters.type) {
      where.type = filters.type
    }
    
    // 年龄范围过滤
    if (filters.minAge || filters.maxAge) {
      where.age = {}
      if (filters.minAge) {
        where.age[Op.gte] = filters.minAge
      }
      if (filters.maxAge) {
        where.age[Op.lte] = filters.maxAge
      }
    }
    
    // 地区过滤
    if (filters.location) {
      where.location = {
        [Op.like]: `%${filters.location}%`
      }
    }
    
    // 关键词搜索
    if (filters.keyword) {
      where[Op.or] = [
        { name: { [Op.like]: `%${filters.keyword}%` } },
        { description: { [Op.like]: `%${filters.keyword}%` } },
        { breed: { [Op.like]: `%${filters.keyword}%` } }
      ]
    }
    
    // 日期范围过滤
    if (filters.dateFrom || filters.dateTo) {
      where.created_at = {}
      if (filters.dateFrom) {
        where.created_at[Op.gte] = new Date(filters.dateFrom)
      }
      if (filters.dateTo) {
        where.created_at[Op.lte] = new Date(filters.dateTo)
      }
    }
    
    return where
  }
  
  // 构建INCLUDE子句
  static buildIncludeClause(include) {
    const includes = []
    
    if (include.includes('images')) {
      includes.push({
        model: require('./AnimalImage'),
        as: 'images',
        attributes: ['id', 'url', 'is_primary'],
        where: { is_deleted: false },
        required: false,
        // 只获取主图片以提高性能
        limit: 1,
        order: [['is_primary', 'DESC'], ['created_at', 'ASC']]
      })
    }
    
    if (include.includes('shelter')) {
      includes.push({
        model: require('./Shelter'),
        as: 'shelter',
        attributes: ['id', 'name', 'location', 'contact_phone']
      })
    }
    
    if (include.includes('adoptions')) {
      includes.push({
        model: require('./Adoption'),
        as: 'adoptions',
        attributes: ['id', 'status', 'created_at'],
        where: { status: { [Op.ne]: 'cancelled' } },
        required: false
      })
    }
    
    return includes
  }
  
  // 批量更新优化
  static async batchUpdate(updates) {
    const transaction = await sequelize.transaction()
    
    try {
      const results = []
      
      // 分批处理,避免单次更新过多记录
      const batchSize = 100
      
      for (let i = 0; i < updates.length; i += batchSize) {
        const batch = updates.slice(i, i + batchSize)
        
        const batchPromises = batch.map(update => {
          return this.update(
            update.data,
            {
              where: { id: update.id },
              transaction,
              // 只返回受影响的行数,不返回完整记录
              returning: false
            }
          )
        })
        
        const batchResults = await Promise.all(batchPromises)
        results.push(...batchResults)
      }
      
      await transaction.commit()
      return results
    } catch (error) {
      await transaction.rollback()
      throw error
    }
  }
  
  // 统计查询优化
  static async getStatistics(filters = {}) {
    const where = this.buildWhereClause(filters)
    
    // 使用原生SQL进行复杂统计查询
    const [results] = await sequelize.query(`
      SELECT 
        COUNT(*) as total_count,
        COUNT(CASE WHEN status = 'available' THEN 1 END) as available_count,
        COUNT(CASE WHEN status = 'adopted' THEN 1 END) as adopted_count,
        COUNT(CASE WHEN status = 'pending' THEN 1 END) as pending_count,
        AVG(age) as average_age,
        COUNT(CASE WHEN type = 'dog' THEN 1 END) as dog_count,
        COUNT(CASE WHEN type = 'cat' THEN 1 END) as cat_count,
        COUNT(CASE WHEN created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY) THEN 1 END) as recent_count
      FROM animals 
      WHERE ${this.buildSQLWhereClause(where)}
    `)
    
    return results[0]
  }
  
  // 构建SQL WHERE子句
  static buildSQLWhereClause(where) {
    // 这里需要根据实际的where对象构建SQL条件
    // 简化示例
    return '1=1' // 实际实现需要更复杂的逻辑
  }
}

// 定义模型
Animal.init({
  id: {
    type: DataTypes.INTEGER,
    primaryKey: true,
    autoIncrement: true
  },
  name: {
    type: DataTypes.STRING(100),
    allowNull: false,
    validate: {
      notEmpty: true,
      len: [1, 100]
    }
  },
  type: {
    type: DataTypes.ENUM('dog', 'cat', 'other'),
    allowNull: false,
    // 添加索引
    index: true
  },
  breed: {
    type: DataTypes.STRING(100),
    allowNull: true
  },
  age: {
    type: DataTypes.INTEGER,
    allowNull: true,
    validate: {
      min: 0,
      max: 30
    },
    // 添加索引用于范围查询
    index: true
  },
  gender: {
    type: DataTypes.ENUM('male', 'female', 'unknown'),
    allowNull: false
  },
  size: {
    type: DataTypes.ENUM('small', 'medium', 'large'),
    allowNull: false
  },
  color: {
    type: DataTypes.STRING(50),
    allowNull: true
  },
  description: {
    type: DataTypes.TEXT,
    allowNull: true
  },
  health_status: {
    type: DataTypes.STRING(200),
    allowNull: true
  },
  vaccination_status: {
    type: DataTypes.BOOLEAN,
    defaultValue: false
  },
  sterilization_status: {
    type: DataTypes.BOOLEAN,
    defaultValue: false
  },
  status: {
    type: DataTypes.ENUM('available', 'adopted', 'pending', 'unavailable'),
    allowNull: false,
    defaultValue: 'available',
    // 添加索引
    index: true
  },
  location: {
    type: DataTypes.STRING(200),
    allowNull: true,
    // 添加索引用于地区查询
    index: true
  },
  shelter_id: {
    type: DataTypes.INTEGER,
    allowNull: true,
    references: {
      model: 'shelters',
      key: 'id'
    },
    // 外键索引
    index: true
  },
  rescue_date: {
    type: DataTypes.DATE,
    allowNull: true
  },
  is_deleted: {
    type: DataTypes.BOOLEAN,
    defaultValue: false,
    // 软删除索引
    index: true
  },
  created_at: {
    type: DataTypes.DATE,
    allowNull: false,
    defaultValue: DataTypes.NOW,
    // 时间索引
    index: true
  },
  updated_at: {
    type: DataTypes.DATE,
    allowNull: false,
    defaultValue: DataTypes.NOW
  }
}, {
  sequelize,
  modelName: 'Animal',
  tableName: 'animals',
  timestamps: true,
  createdAt: 'created_at',
  updatedAt: 'updated_at',
  // 复合索引
  indexes: [
    {
      name: 'idx_animals_status_type',
      fields: ['status', 'type']
    },
    {
      name: 'idx_animals_location_status',
      fields: ['location', 'status']
    },
    {
      name: 'idx_animals_created_status',
      fields: ['created_at', 'status']
    },
    {
      name: 'idx_animals_age_type',
      fields: ['age', 'type']
    }
  ],
  // 默认作用域
  defaultScope: {
    where: {
      is_deleted: false
    }
  },
  // 命名作用域
  scopes: {
    available: {
      where: {
        status: 'available',
        is_deleted: false
      }
    },
    withImages: {
      include: [{
        model: require('./AnimalImage'),
        as: 'images',
        where: { is_deleted: false },
        required: false
      }]
    }
  }
})

module.exports = Animal

缓存策略实现

Redis缓存服务

// services/CacheService.js - 缓存服务
const Redis = require('ioredis')
const logger = require('../utils/logger')

class CacheService {
  constructor() {
    this.redis = new Redis({
      host: process.env.REDIS_HOST || 'localhost',
      port: process.env.REDIS_PORT || 6379,
      password: process.env.REDIS_PASSWORD,
      db: process.env.REDIS_DB || 0,
      retryDelayOnFailover: 100,
      maxRetriesPerRequest: 3,
      lazyConnect: true,
      // 连接池配置
      family: 4,
      keepAlive: true,
      // 集群配置如果使用Redis集群
      enableOfflineQueue: false
    })
    
    // 缓存配置
    this.config = {
      // 默认过期时间(秒)
      defaultTTL: 3600, // 1小时
      
      // 不同类型数据的TTL
      ttl: {
        user: 1800,        // 用户信息 30分钟
        animal: 3600,      // 动物信息 1小时
        statistics: 300,   // 统计数据 5分钟
        search: 600,       // 搜索结果 10分钟
        session: 86400,    // 会话 24小时
        config: 7200       // 配置信息 2小时
      },
      
      // 缓存键前缀
      prefix: {
        user: 'user:',
        animal: 'animal:',
        list: 'list:',
        search: 'search:',
        statistics: 'stats:',
        session: 'session:',
        lock: 'lock:'
      }
    }
    
    this.setupEventHandlers()
  }
  
  // 设置事件处理器
  setupEventHandlers() {
    this.redis.on('connect', () => {
      logger.info('Redis connected')
    })
    
    this.redis.on('error', (error) => {
      logger.error('Redis error:', error)
    })
    
    this.redis.on('close', () => {
      logger.warn('Redis connection closed')
    })
  }
  
  // 生成缓存键
  generateKey(type, identifier, suffix = '') {
    const prefix = this.config.prefix[type] || ''
    return `${prefix}${identifier}${suffix ? ':' + suffix : ''}`
  }
  
  // 设置缓存
  async set(key, value, ttl = null) {
    try {
      const serializedValue = JSON.stringify(value)
      const expireTime = ttl || this.config.defaultTTL
      
      await this.redis.setex(key, expireTime, serializedValue)
      
      logger.debug(`Cache set: ${key} (TTL: ${expireTime}s)`)
      return true
    } catch (error) {
      logger.error('Cache set error:', error)
      return false
    }
  }
  
  // 获取缓存
  async get(key) {
    try {
      const value = await this.redis.get(key)
      
      if (value === null) {
        logger.debug(`Cache miss: ${key}`)
        return null
      }
      
      logger.debug(`Cache hit: ${key}`)
      return JSON.parse(value)
    } catch (error) {
      logger.error('Cache get error:', error)
      return null
    }
  }
  
  // 删除缓存
  async del(key) {
    try {
      const result = await this.redis.del(key)
      logger.debug(`Cache deleted: ${key}`)
      return result > 0
    } catch (error) {
      logger.error('Cache delete error:', error)
      return false
    }
  }
  
  // 批量删除缓存
  async delPattern(pattern) {
    try {
      const keys = await this.redis.keys(pattern)
      
      if (keys.length > 0) {
        const result = await this.redis.del(...keys)
        logger.debug(`Cache pattern deleted: ${pattern} (${keys.length} keys)`)
        return result
      }
      
      return 0
    } catch (error) {
      logger.error('Cache pattern delete error:', error)
      return 0
    }
  }
  
  // 检查缓存是否存在
  async exists(key) {
    try {
      const result = await this.redis.exists(key)
      return result === 1
    } catch (error) {
      logger.error('Cache exists check error:', error)
      return false
    }
  }
  
  // 设置缓存过期时间
  async expire(key, ttl) {
    try {
      const result = await this.redis.expire(key, ttl)
      return result === 1
    } catch (error) {
      logger.error('Cache expire error:', error)
      return false
    }
  }
  
  // 获取或设置缓存(缓存穿透保护)
  async getOrSet(key, fetchFunction, ttl = null) {
    try {
      // 先尝试从缓存获取
      let value = await this.get(key)
      
      if (value !== null) {
        return value
      }
      
      // 使用分布式锁防止缓存击穿
      const lockKey = this.generateKey('lock', key)
      const lockAcquired = await this.acquireLock(lockKey, 10) // 10秒锁
      
      if (!lockAcquired) {
        // 如果获取锁失败,等待一段时间后重试
        await this.sleep(100)
        value = await this.get(key)
        if (value !== null) {
          return value
        }
      }
      
      try {
        // 执行数据获取函数
        value = await fetchFunction()
        
        // 缓存结果即使是null也要缓存防止缓存穿透
        const cacheValue = value !== null ? value : { __null: true }
        const expireTime = ttl || this.getTTL(key)
        
        await this.set(key, cacheValue, expireTime)
        
        return value
      } finally {
        // 释放锁
        if (lockAcquired) {
          await this.releaseLock(lockKey)
        }
      }
    } catch (error) {
      logger.error('Cache getOrSet error:', error)
      // 如果缓存操作失败,直接执行函数
      return await fetchFunction()
    }
  }
  
  // 获取TTL
  getTTL(key) {
    // 根据键名确定TTL
    for (const [type, prefix] of Object.entries(this.config.prefix)) {
      if (key.startsWith(prefix)) {
        return this.config.ttl[type] || this.config.defaultTTL
      }
    }
    return this.config.defaultTTL
  }
  
  // 获取分布式锁
  async acquireLock(lockKey, expireTime = 10) {
    try {
      const result = await this.redis.set(lockKey, '1', 'EX', expireTime, 'NX')
      return result === 'OK'
    } catch (error) {
      logger.error('Lock acquire error:', error)
      return false
    }
  }
  
  // 释放分布式锁
  async releaseLock(lockKey) {
    try {
      await this.redis.del(lockKey)
      return true
    } catch (error) {
      logger.error('Lock release error:', error)
      return false
    }
  }
  
  // 睡眠函数
  sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms))
  }
  
  // 缓存用户信息
  async cacheUser(userId, userData) {
    const key = this.generateKey('user', userId)
    return await this.set(key, userData, this.config.ttl.user)
  }
  
  // 获取用户缓存
  async getUser(userId) {
    const key = this.generateKey('user', userId)
    return await this.get(key)
  }
  
  // 删除用户缓存
  async deleteUser(userId) {
    const key = this.generateKey('user', userId)
    return await this.del(key)
  }
  
  // 缓存动物列表
  async cacheAnimalList(filters, data) {
    const key = this.generateKey('list', 'animals', this.hashFilters(filters))
    return await this.set(key, data, this.config.ttl.animal)
  }
  
  // 获取动物列表缓存
  async getAnimalList(filters) {
    const key = this.generateKey('list', 'animals', this.hashFilters(filters))
    return await this.get(key)
  }
  
  // 删除动物相关缓存
  async deleteAnimalCache(animalId = null) {
    if (animalId) {
      // 删除特定动物缓存
      const key = this.generateKey('animal', animalId)
      await this.del(key)
    }
    
    // 删除所有动物列表缓存
    await this.delPattern(this.generateKey('list', 'animals', '*'))
  }
  
  // 哈希过滤器参数
  hashFilters(filters) {
    const crypto = require('crypto')
    const filterString = JSON.stringify(filters, Object.keys(filters).sort())
    return crypto.createHash('md5').update(filterString).digest('hex')
  }
  
  // 缓存统计数据
  async cacheStatistics(type, data) {
    const key = this.generateKey('statistics', type)
    return await this.set(key, data, this.config.ttl.statistics)
  }
  
  // 获取统计缓存
  async getStatistics(type) {
    const key = this.generateKey('statistics', type)
    return await this.get(key)
  }
  
  // 批量操作
  async mget(keys) {
    try {
      const values = await this.redis.mget(...keys)
      return values.map(value => value ? JSON.parse(value) : null)
    } catch (error) {
      logger.error('Cache mget error:', error)
      return new Array(keys.length).fill(null)
    }
  }
  
  async mset(keyValuePairs, ttl = null) {
    try {
      const pipeline = this.redis.pipeline()
      const expireTime = ttl || this.config.defaultTTL
      
      for (const [key, value] of keyValuePairs) {
        pipeline.setex(key, expireTime, JSON.stringify(value))
      }
      
      await pipeline.exec()
      return true
    } catch (error) {
      logger.error('Cache mset error:', error)
      return false
    }
  }
  
  // 关闭连接
  async close() {
    await this.redis.quit()
  }
}

module.exports = new CacheService()

API响应优化

响应压缩和优化中间件

// middleware/optimization.js - 性能优化中间件
const compression = require('compression')
const helmet = require('helmet')
const rateLimit = require('express-rate-limit')
const slowDown = require('express-slow-down')
const responseTime = require('response-time')
const cacheService = require('../services/CacheService')
const logger = require('../utils/logger')

// 响应压缩中间件
const compressionMiddleware = compression({
  // 压缩级别 (1-9, 9最高)
  level: 6,
  // 压缩阈值小于1KB的响应不压缩
  threshold: 1024,
  // 过滤器函数
  filter: (req, res) => {
    // 不压缩已经压缩的内容
    if (req.headers['x-no-compression']) {
      return false
    }
    
    // 只压缩文本内容
    const contentType = res.getHeader('content-type')
    if (contentType) {
      return /text|json|javascript|css|xml|svg/.test(contentType)
    }
    
    return compression.filter(req, res)
  }
})

// 安全头中间件
const securityMiddleware = helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
      fontSrc: ["'self'", "https://fonts.gstatic.com"],
      imgSrc: ["'self'", "data:", "https:"],
      scriptSrc: ["'self'"],
      connectSrc: ["'self'", "https://api.jiebanke.com"]
    }
  },
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true
  }
})

// 速率限制中间件
const rateLimitMiddleware = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分钟
  max: 1000, // 每个IP最多1000次请求
  message: {
    error: 'Too many requests',
    message: '请求过于频繁,请稍后再试'
  },
  standardHeaders: true,
  legacyHeaders: false,
  // 自定义键生成器
  keyGenerator: (req) => {
    // 优先使用用户ID其次使用IP
    return req.user?.id || req.ip
  },
  // 跳过成功的请求
  skipSuccessfulRequests: false,
  // 跳过失败的请求
  skipFailedRequests: true
})

// API速率限制更严格
const apiRateLimitMiddleware = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分钟
  max: 500, // API请求限制更严格
  message: {
    error: 'API rate limit exceeded',
    message: 'API请求频率超限请稍后再试'
  }
})

// 慢速响应中间件
const slowDownMiddleware = slowDown({
  windowMs: 15 * 60 * 1000, // 15分钟
  delayAfter: 100, // 100次请求后开始延迟
  delayMs: 500, // 每次增加500ms延迟
  maxDelayMs: 20000 // 最大延迟20秒
})

// 响应时间中间件
const responseTimeMiddleware = responseTime((req, res, time) => {
  // 记录慢查询
  if (time > 1000) { // 超过1秒
    logger.warn('Slow request detected', {
      method: req.method,
      url: req.url,
      responseTime: time,
      userAgent: req.get('User-Agent'),
      ip: req.ip
    })
  }
  
  // 添加响应时间头
  res.set('X-Response-Time', `${time}ms`)
})

// 缓存中间件
const cacheMiddleware = (options = {}) => {
  const {
    ttl = 300, // 默认5分钟
    keyGenerator = (req) => `${req.method}:${req.originalUrl}`,
    condition = () => true,
    vary = ['Accept-Encoding']
  } = options
  
  return async (req, res, next) => {
    // 只缓存GET请求
    if (req.method !== 'GET') {
      return next()
    }
    
    // 检查缓存条件
    if (!condition(req)) {
      return next()
    }
    
    const cacheKey = keyGenerator(req)
    
    try {
      // 尝试从缓存获取
      const cachedResponse = await cacheService.get(cacheKey)
      
      if (cachedResponse) {
        // 设置缓存头
        res.set('X-Cache', 'HIT')
        res.set('Cache-Control', `public, max-age=${ttl}`)
        
        // 设置Vary头
        if (vary.length > 0) {
          res.vary(vary)
        }
        
        // 返回缓存的响应
        return res.status(cachedResponse.status).json(cachedResponse.data)
      }
      
      // 缓存未命中,继续处理请求
      res.set('X-Cache', 'MISS')
      
      // 拦截响应
      const originalJson = res.json
      res.json = function(data) {
        // 只缓存成功的响应
        if (res.statusCode >= 200 && res.statusCode < 300) {
          const responseData = {
            status: res.statusCode,
            data: data
          }
          
          // 异步缓存,不阻塞响应
          cacheService.set(cacheKey, responseData, ttl).catch(error => {
            logger.error('Cache set error:', error)
          })
        }
        
        // 调用原始json方法
        return originalJson.call(this, data)
      }
      
      next()
    } catch (error) {
      logger.error('Cache middleware error:', error)
      next()
    }
  }
}

// ETag中间件
const etagMiddleware = (req, res, next) => {
  const originalJson = res.json
  
  res.json = function(data) {
    if (req.method === 'GET' && res.statusCode === 200) {
      const crypto = require('crypto')
      const etag = crypto.createHash('md5').update(JSON.stringify(data)).digest('hex')
      
      res.set('ETag', `"${etag}"`)
      
      // 检查If-None-Match头
      const clientEtag = req.get('If-None-Match')
      if (clientEtag === `"${etag}"`) {
        return res.status(304).end()
      }
    }
    
    return originalJson.call(this, data)
  }
  
  next()
}

// 请求日志中间件
const requestLogMiddleware = (req, res, next) => {
  const startTime = Date.now()
  
  // 记录请求开始
  logger.info('Request started', {
    method: req.method,
    url: req.url,
    ip: req.ip,
    userAgent: req.get('User-Agent'),
    contentLength: req.get('Content-Length')
  })
  
  // 监听响应结束
  res.on('finish', () => {
    const duration = Date.now() - startTime
    
    logger.info('Request completed', {
      method: req.method,
      url: req.url,
      statusCode: res.statusCode,
      duration: duration,
      contentLength: res.get('Content-Length')
    })
  })
  
  next()
}

// 健康检查中间件
const healthCheckMiddleware = (req, res, next) => {
  if (req.path === '/health' || req.path === '/ping') {
    return res.json({
      status: 'ok',
      timestamp: new Date().toISOString(),
      uptime: process.uptime(),
      memory: process.memoryUsage(),
      version: process.env.npm_package_version || '1.0.0'
    })
  }
  
  next()
}

// 错误处理中间件
const errorHandlingMiddleware = (error, req, res, next) => {
  logger.error('Request error', {
    error: error.message,
    stack: error.stack,
    method: req.method,
    url: req.url,
    ip: req.ip
  })
  
  // 根据错误类型返回不同响应
  if (error.name === 'ValidationError') {
    return res.status(400).json({
      error: 'Validation Error',
      message: error.message,
      details: error.details
    })
  }
  
  if (error.name === 'UnauthorizedError') {
    return res.status(401).json({
      error: 'Unauthorized',
      message: '认证失败'
    })
  }
  
  if (error.name === 'ForbiddenError') {
    return res.status(403).json({
      error: 'Forbidden',
      message: '权限不足'
    })
  }
  
  if (error.name === 'NotFoundError') {
    return res.status(404).json({
      error: 'Not Found',
      message: '资源不存在'
    })
  }
  
  // 默认服务器错误
  res.status(500).json({
    error: 'Internal Server Error',
    message: process.env.NODE_ENV === 'production' ? '服务器内部错误' : error.message
  })
}

module.exports = {
  compressionMiddleware,
  securityMiddleware,
  rateLimitMiddleware,
  apiRateLimitMiddleware,
  slowDownMiddleware,
  responseTimeMiddleware,
  cacheMiddleware,
  etagMiddleware,
  requestLogMiddleware,
  healthCheckMiddleware,
  errorHandlingMiddleware
}

📊 监控和分析

性能监控仪表板

// utils/PerformanceAnalyzer.js - 性能分析工具
class PerformanceAnalyzer {
  constructor() {
    this.metrics = new Map()
    this.alerts = []
    this.thresholds = {
      responseTime: {
        warning: 500,  // 500ms
        critical: 1000 // 1s
      },
      errorRate: {
        warning: 0.01,  // 1%
        critical: 0.05  // 5%
      },
      throughput: {
        warning: 100,   // 100 RPS
        critical: 50    // 50 RPS
      },
      memoryUsage: {
        warning: 0.8,   // 80%
        critical: 0.9   // 90%
      }
    }
  }
  
  // 记录性能指标
  recordMetric(name, value, tags = {}) {
    const timestamp = Date.now()
    const metric = {
      name,
      value,
      timestamp,
      tags
    }
    
    if (!this.metrics.has(name)) {
      this.metrics.set(name, [])
    }
    
    this.metrics.get(name).push(metric)
    
    // 保持最近1000条记录
    const records = this.metrics.get(name)
    if (records.length > 1000) {
      records.splice(0, records.length - 1000)
    }
    
    // 检查阈值
    this.checkThresholds(name, value, tags)
  }
  
  // 检查阈值
  checkThresholds(metricName, value, tags) {
    const threshold = this.thresholds[metricName]
    if (!threshold) return
    
    let level = null
    if (value >= threshold.critical) {
      level = 'critical'
    } else if (value >= threshold.warning) {
      level = 'warning'
    }
    
    if (level) {
      this.triggerAlert({
        metric: metricName,
        value,
        level,
        threshold: threshold[level],
        tags,
        timestamp: Date.now()
      })
    }
  }
  
  // 触发告警
  triggerAlert(alert) {
    this.alerts.push(alert)
    
    // 保持最近100条告警
    if (this.alerts.length > 100) {
      this.alerts.splice(0, this.alerts.length - 100)
    }
    
    // 发送告警通知
    this.sendAlert(alert)
  }
  
  // 发送告警
  async sendAlert(alert) {
    const message = `性能告警: ${alert.metric} = ${alert.value} (阈值: ${alert.threshold})`
    
    console.warn(message, alert)
    
    // 这里可以集成邮件、短信、钉钉等告警通道
    try {
      // 发送到监控系统
      await fetch('/api/monitoring/alerts', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(alert)
      })
    } catch (error) {
      console.error('Failed to send alert:', error)
    }
  }
  
  // 获取性能报告
  getPerformanceReport(timeRange = 3600000) { // 默认1小时
    const now = Date.now()
    const startTime = now - timeRange
    
    const report = {
      timeRange: {
        start: new Date(startTime).toISOString(),
        end: new Date(now).toISOString()
      },
      metrics: {},
      alerts: this.alerts.filter(alert => alert.timestamp >= startTime),
      summary: {}
    }
    
    // 分析各项指标
    for (const [name, records] of this.metrics.entries()) {
      const filteredRecords = records.filter(record => record.timestamp >= startTime)
      
      if (filteredRecords.length === 0) continue
      
      const values = filteredRecords.map(record => record.value)
      
      report.metrics[name] = {
        count: filteredRecords.length,
        min: Math.min(...values),
        max: Math.max(...values),
        avg: values.reduce((sum, val) => sum + val, 0) / values.length,
        p50: this.percentile(values, 0.5),
        p95: this.percentile(values, 0.95),
        p99: this.percentile(values, 0.99)
      }
    }
    
    // 生成摘要
    report.summary = this.generateSummary(report)
    
    return report
  }
  
  // 计算百分位数
  percentile(values, p) {
    const sorted = values.slice().sort((a, b) => a - b)
    const index = Math.ceil(sorted.length * p) - 1
    return sorted[index] || 0
  }
  
  // 生成性能摘要
  generateSummary(report) {
    const summary = {
      status: 'healthy',
      issues: [],
      recommendations: []
    }
    
    // 检查响应时间
    const responseTime = report.metrics.responseTime
    if (responseTime) {
      if (responseTime.p95 > this.thresholds.responseTime.critical) {
        summary.status = 'critical'
        summary.issues.push('95%的请求响应时间超过1秒')
        summary.recommendations.push('优化数据库查询和缓存策略')
      } else if (responseTime.p95 > this.thresholds.responseTime.warning) {
        summary.status = 'warning'
        summary.issues.push('95%的请求响应时间超过500ms')
        summary.recommendations.push('检查慢查询和网络延迟')
      }
    }
    
    // 检查错误率
    const errorRate = report.metrics.errorRate
    if (errorRate && errorRate.avg > this.thresholds.errorRate.warning) {
      summary.status = summary.status === 'critical' ? 'critical' : 'warning'
      summary.issues.push(`错误率过高: ${(errorRate.avg * 100).toFixed(2)}%`)
      summary.recommendations.push('检查应用日志和错误处理')
    }
    
    // 检查内存使用
    const memoryUsage = report.metrics.memoryUsage
    if (memoryUsage && memoryUsage.max > this.thresholds.memoryUsage.critical) {
      summary.status = 'critical'
      summary.issues.push('内存使用率超过90%')
      summary.recommendations.push('检查内存泄漏和优化内存使用')
    }
    
    return summary
  }
}

module.exports = PerformanceAnalyzer

🎯 总结

本性能优化文档提供了解班客项目的全方位性能优化方案,包括:

核心优化策略

  1. 前端优化:代码分割、懒加载、虚拟滚动、图片优化
  2. 后端优化数据库查询优化、缓存策略、API响应优化
  3. 监控体系:性能指标监控、告警机制、性能分析

性能目标

  • 页面加载时间 < 2.5秒
  • API响应时间 < 500ms (95%)
  • 系统可用性 > 99.9%
  • 并发用户数 > 500

下一步计划

  1. 实施性能监控系统
  2. 优化关键路径性能
  3. 建立性能基准测试
  4. 持续性能优化迭代

通过系统性的性能优化,确保解班客项目能够为用户提供快速、稳定、高质量的服务体验。