60 KiB
60 KiB
解班客项目性能优化文档
📋 文档概述
本文档详细说明解班客项目的性能优化策略、监控方案和优化实践,涵盖前端、后端、数据库和基础设施的全方位性能优化。
文档目标
- 建立完整的性能监控体系
- 提供系统性的性能优化方案
- 制定性能基准和优化目标
- 建立性能问题诊断和解决流程
🎯 性能目标和指标
核心性能指标
前端性能指标
// 前端性能目标
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
🎯 总结
本性能优化文档提供了解班客项目的全方位性能优化方案,包括:
核心优化策略
- 前端优化:代码分割、懒加载、虚拟滚动、图片优化
- 后端优化:数据库查询优化、缓存策略、API响应优化
- 监控体系:性能指标监控、告警机制、性能分析
性能目标
- 页面加载时间 < 2.5秒
- API响应时间 < 500ms (95%)
- 系统可用性 > 99.9%
- 并发用户数 > 500
下一步计划
- 实施性能监控系统
- 优化关键路径性能
- 建立性能基准测试
- 持续性能优化迭代
通过系统性的性能优化,确保解班客项目能够为用户提供快速、稳定、高质量的服务体验。