游戏研发 CI/CD 流水线:构建高效交付体系

深入探讨游戏研发的CI/CD流水线设计,包括自动化构建、测试、发布等全流程

CI/CD流水线设计最佳实践:从构建到部署的完整优化指南

深入探讨CI/CD流水线设计的核心原则、性能优化策略和安全实践,帮助你构建高效可靠的生产级持续集成交付系统。

Jenkins vs GitLab CI:两大CI/CD平台的深度对比与选型指南

详细对比Jenkins和GitLab CI的架构差异、功能特性、使用场景和性能表现,帮助你做出正确的技术选型决策。

GitHub Actions 实战指南:从入门到精通的完整CI/CD流水线

深入讲解GitHub Actions的核心概念、工作流语法和最佳实践,帮助你构建高效的自动化CI/CD流水线。

Serverless架构与前端部署:现代化部署策略

Serverless架构概述 Serverless架构让开发者无需管理服务器即可部署应用,主要优势包括: 按需付费 - 只为实际使用的资源付费 自动扩展 - 根据流量自动调整 零运维 - 无需管理服务器 全球CDN - 内容分发网络加速 Vercel部署 项目配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 // vercel.json { "buildCommand": "npm run build", "outputDirectory": ".output/public", "devCommand": "npm run dev", "installCommand": "npm install", "framework": "nuxt", "regions": ["hkg1", "sin1"], "headers": [ { "source": "/api/(.*)", "headers": [ { "key": "Access-Control-Allow-Origin", "value": "*" } ] } ], "rewrites": [ { "source": "/api/:path*", "destination": "/api/:path*" } ] } 环境变量 1 2 3 4 # 在Vercel控制台设置环境变量 # 或通过vercel CLI vercel env add DATABASE_URL production vercel env add API_KEY production Serverless函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 // api/hello.js export default function handler(req, res) { const { name } = req.query res.status(200).json({ message: `Hello ${name || 'World'}!`, timestamp: Date.now() }) } // api/users/[id].js export default function handler(req, res) { const { id } = req.query switch (req.method) { case 'GET': res.status(200).json({ id, name: `User ${id}` }) break case 'PUT': res.status(200).json({ id, ...req.body }) break default: res.setHeader('Allow', ['GET', 'PUT']) res.status(405).end(`Method ${req.method} Not Allowed`) } } Netlify部署 netlify.toml配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 [build] command = "npm run build" publish = "dist" [build.environment] NODE_VERSION = "18" [[redirects]] from = "/api/*" to = "/.netlify/functions/:splat" status = 200 [[redirects]] from = "/*" to = "/index.html" status = 200 [functions] directory = "netlify/functions" [[plugins]] package = "@netlify/plugin-lighthouse" Netlify Functions 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 // netlify/functions/hello.js exports.handler = async (event, context) => { const { name } = event.queryStringParameters || {} return { statusCode: 200, body: JSON.stringify({ message: `Hello ${name || 'World'}!` }), headers: { 'Content-Type': 'application/json' } } } // netlify/functions/users/[id].js exports.handler = async (event, context) => { const { id } = event.pathParameters switch (event.httpMethod) { case 'GET': return { statusCode: 200, body: JSON.stringify({ id, name: `User ${id}` }) } default: return { statusCode: 405, body: 'Method Not Allowed' } } } Cloudflare Workers Worker配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 // worker.js export default { async fetch(request, env, ctx) { const url = new URL(request.url) // API路由 if (url.pathname.startsWith('/api/')) { return handleAPI(request, env) } // 静态资源 return env.ASSETS.fetch(request) } } async function handleAPI(request, env) { const url = new URL(request.url) const path = url.pathname.replace('/api/', '') switch (path) { case 'hello': return new Response(JSON.stringify({ message: 'Hello from Cloudflare!' }), { headers: { 'Content-Type': 'application/json' } }) default: return new Response('Not Found', { status: 404 }) } } wrangler.toml配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 name = "my-worker" main = "worker.js" compatibility_date = "2024-01-01" [env.production] routes = [ { pattern = "https://example.com/api/*", zone_name = "example.com" } ] [env.production.vars] ENVIRONMENT = "production" API_URL = "https://api.example.com" [[env.production.kv_namespaces]] binding = "KV" id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" CI/CD配置 GitHub Actions 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 # .github/workflows/deploy.yml name: Deploy on: push: branches: [main] pull_request: branches: [main] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '18' - name: Install dependencies run: npm ci - name: Run tests run: npm test - name: Build run: npm run build deploy-vercel: needs: test if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Deploy to Vercel uses: amondnet/vercel-action@v20 with: vercel-token: ${{ secrets.VERCEL_TOKEN }} vercel-org-id: ${{ secrets.ORG_ID }} vercel-project-id: ${{ secrets.PROJECT_ID }} vercel-args: '--prod' deploy-netlify: needs: test if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Deploy to Netlify uses: nwtgck/actions-netlify@v1.2 with: publish-dir: './dist' production-branch: main github-token: ${{ secrets.GITHUB_TOKEN }} deploy-message: "Deploy from GitHub Actions" env: NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} GitLab CI 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 # .gitlab-ci.yml stages: - test - build - deploy test: stage: test image: node:18 script: - npm ci - npm test build: stage: build image: node:18 script: - npm ci - npm run build artifacts: paths: - dist deploy: stage: deploy image: node:18 script: - npm install -g vercel - vercel --prod --token=$VERCEL_TOKEN only: - main Docker部署 Dockerfile 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 # 多阶段构建 FROM node:18-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build # 生产镜像 FROM node:18-alpine WORKDIR /app COPY package*.json ./ RUN npm ci --production COPY --from=builder /app/.output ./.output EXPOSE 3000 CMD ["node", ".output/server/index.mjs"] docker-compose.yml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 version: '3.8' services: app: build: . ports: - "3000:3000" environment: - NODE_ENV=production - DATABASE_URL=${DATABASE_URL} restart: unless-stopped nginx: image: nginx:alpine ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf - ./ssl:/etc/nginx/ssl depends_on: - app 性能优化 CDN配置 1 2 3 4 5 6 7 8 9 10 11 12 13 // nuxt.config.ts export default defineNuxtConfig({ app: { cdnURL: 'https://cdn.example.com' }, nitro: { routeRules: { '/': { cache: { maxAge: 3600 } }, '/api/**': { cache: { maxAge: 60 } }, '/_nuxt/**': { cache: { maxAge: 31536000 } } } } }) 图片优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <script setup> // 使用Nuxt Image组件 import { NuxtImg } from '#components' </script> <template> <NuxtImg src="/image.jpg" width="500" height="300" format="webp" loading="lazy" placeholder /> </template> 监控和日志 性能监控 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // 服务端性能追踪 export default defineEventHandler(async (event) => { const start = Date.now() // 处理请求 const result = await fetchData() const duration = Date.now() - start // 上报性能数据 $fetch('/api/analytics', { method: 'POST', body: { path: event.path, duration, timestamp: Date.now() } }) return result }) 错误追踪 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // 集成Sentry import * as Sentry from '@sentry/node' Sentry.init({ dsn: process.env.SENTRY_DSN, environment: process.env.NODE_ENV, tracesSampleRate: 0.2 }) export default defineEventHandler(async (event) => { try { return await fetchData() } catch (error) { Sentry.captureException(error) throw error } }) 最佳实践 自动化部署 - 使用CI/CD自动部署 环境隔离 - 区分开发、测试、生产环境 监控告警 - 设置性能和错误监控 备份策略 - 定期备份重要数据 灾难恢复 - 准备回滚方案 总结 Serverless架构为前端部署提供了灵活、高效的解决方案。通过合理选择平台和配置CI/CD流程,可以显著提升部署效率和应用的稳定性。 ...

DevOps与CI/CD完全指南:构建高效的软件交付流水线

引言 在当今快速迭代的软件开发环境中,DevOps实践和CI/CD流水线已成为团队提高交付效率、保证软件质量的关键。本文将深入探讨如何构建完整的DevOps体系,从代码提交到生产部署的全流程自动化实践。 一、DevOps核心理念 1.1 DevOps的CALMS模型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 # ========== DevOps CALMS模型 ========== """ C - Culture (文化) ├── 协作精神 ├── 持续改进 ├── 容错心态 └── 透明沟通 A - Automation (自动化) ├── 构建自动化 ├── 测试自动化 ├── 部署自动化 └── 监控自动化 L - Lean (精益) ├── 消除浪费 ├── 持续交付 ├── 快速反馈 └── 价值流优化 M - Measurement (度量) ├── 关键指标追踪 ├── 数据驱动决策 ├── 持续监控 └── 效果评估 S - Sharing (分享) ├── 知识共享 ├── 最佳实践传播 ├── 工具共享 └── 经验总结 """ # DevOps成熟度评估模型 DEVOPS_MATURITY_LEVELS = { "Level 1 - 初始级": { "characteristics": [ "手动操作为主", "缺乏标准化流程", "开发和运维分离" ], "practices": [], "improvements": [ "建立基础自动化流程", "制定标准化规范" ] }, "Level 2 - 可重复级": { "characteristics": [ "有基本的CI流程", "环境配置初步标准化", "文档化流程" ], "practices": [ "版本控制", "单元测试", "基础自动化构建" ], "improvements": [ "扩展自动化范围", "增加测试覆盖率" ] }, "Level 3 - 已定义级": { "characteristics": [ "完整的CI/CD流水线", "基础设施即代码", "自动化测试体系" ], "practices": [ "持续集成", "持续部署", "自动化测试", "配置管理" ], "improvements": [ "优化部署流程", "增强监控能力" ] }, "Level 4 - 可管理级": { "characteristics": [ "全链路自动化", "完善的监控体系", "快速故障恢复" ], "practices": [ "自动化运维", "监控告警", "故障自愈", "性能优化" ], "improvements": [ "持续优化", "成本控制" ] }, "Level 5 - 优化级": { "characteristics": [ "智能运维", "预测性维护", "持续创新" ], "practices": [ "AI辅助决策", "混沌工程", "自动化优化" ], "improvements": [ "持续演进" ] } } 1.2 DevOps指标体系 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 # ========== DORA指标 ========== class DORAMetrics: """DORA (DevOps Research and Assessment) 核心指标""" @staticmethod def deployment_frequency(deployments: int, days: int) -> float: """ 部署频率 - 精英级:按需部署 (每天多次) - 高绩效:每周1-6个月 - 中等绩效:每月1-6个月 - 低绩效:少于每6个月1次 """ return deployments / days @staticmethod def lead_time_for_changes(commit_time: str, deploy_time: str) -> float: """ 变更前置时间 从代码提交到成功部署的时间 - 精英级:小于1小时 - 高绩效:小于1天 - 中等绩效:1周-1个月 - 低绩效:超过1个月 """ from datetime import datetime commit = datetime.fromisoformat(commit_time) deploy = datetime.fromisoformat(deploy_time) return (deploy - commit).total_seconds() / 3600 # 小时 @staticmethod def time_to_restore_service(incident_time: str, restore_time: str) -> float: """ 服务恢复时间 - 精英级:小于1小时 - 高绩效:小于1天 - 中等绩效:1天-1周 - 低绩效:超过1周 """ from datetime import datetime incident = datetime.fromisoformat(incident_time) restore = datetime.fromisoformat(restore_time) return (restore - incident).total_seconds() / 3600 # 小时 @staticmethod def change_failure_rate(total_deployments: int, failed_deployments: int) -> float: """ 变更失败率 - 精英级:0-15% - 高绩效:15-30% - 中等绩效:30-60% - 低绩效:超过60% """ return (failed_deployments / total_deployments) * 100 @classmethod def evaluate_performance(cls, metrics: dict) -> str: """评估团队DevOps绩效等级""" score = 0 if metrics['deployment_frequency'] >= 1: # 每天至少1次 score += 1 if metrics['lead_time'] <= 1: # 小于1小时 score += 1 if metrics['restore_time'] <= 1: # 小于1小时 score += 1 if metrics['failure_rate'] <= 15: # 小于15% score += 1 levels = { 4: "精英级", 3: "高绩效", 2: "中等绩效", 1: "低绩效", 0: "低绩效" } return levels[score] # ========== 自定义DevOps指标 ========== class DevOpsMetricsCollector: """DevOps指标收集器""" def __init__(self): self.metrics = { 'builds': [], 'deployments': [], 'incidents': [], 'tests': [] } def record_build(self, build_info: dict): """记录构建信息""" self.metrics['builds'].append({ 'timestamp': build_info['timestamp'], 'branch': build_info['branch'], 'commit': build_info['commit'], 'status': build_info['status'], 'duration': build_info['duration'], 'triggered_by': build_info['triggered_by'] }) def record_deployment(self, deployment_info: dict): """记录部署信息""" self.metrics['deployments'].append({ 'timestamp': deployment_info['timestamp'], 'environment': deployment_info['environment'], 'version': deployment_info['version'], 'status': deployment_info['status'], 'duration': deployment_info['duration'], 'deployed_by': deployment_info['deployed_by'] }) def record_incident(self, incident_info: dict): """记录故障信息""" self.metrics['incidents'].append({ 'detected_at': incident_info['detected_at'], 'resolved_at': incident_info.get('resolved_at'), 'severity': incident_info['severity'], 'affected_services': incident_info['affected_services'], 'root_cause': incident_info.get('root_cause') }) def calculate_metrics(self, days: int = 30) -> dict: """计算DevOps指标""" from datetime import datetime, timedelta cutoff_time = datetime.now() - timedelta(days=days) # 筛选时间范围内的数据 recent_builds = [ b for b in self.metrics['builds'] if datetime.fromisoformat(b['timestamp']) > cutoff_time ] recent_deployments = [ d for d in self.metrics['deployments'] if datetime.fromisoformat(d['timestamp']) > cutoff_time ] recent_incidents = [ i for i in self.metrics['incidents'] if datetime.fromisoformat(i['detected_at']) > cutoff_time ] # 计算指标 total_builds = len(recent_builds) successful_builds = len([b for b in recent_builds if b['status'] == 'success']) build_success_rate = (successful_builds / total_builds * 100) if total_builds > 0 else 0 total_deployments = len(recent_deployments) successful_deployments = len([d for d in recent_deployments if d['status'] == 'success']) deployment_success_rate = (successful_deployments / total_deployments * 100) if total_deployments > 0 else 0 deployment_frequency = total_deployments / days total_incidents = len(recent_incidents) resolved_incidents = len([i for i in recent_incidents if i.get('resolved_at')]) avg_resolution_time = 0 if resolved_incidents > 0: resolution_times = [] for incident in recent_incidents: if incident.get('resolved_at'): detected = datetime.fromisoformat(incident['detected_at']) resolved = datetime.fromisoformat(incident['resolved_at']) resolution_times.append((resolved - detected).total_seconds() / 3600) avg_resolution_time = sum(resolution_times) / len(resolution_times) return { 'build_metrics': { 'total_builds': total_builds, 'success_rate': round(build_success_rate, 2), 'avg_duration': round( sum(b['duration'] for b in recent_builds) / total_builds if total_builds > 0 else 0, 2 ) }, 'deployment_metrics': { 'total_deployments': total_deployments, 'success_rate': round(deployment_success_rate, 2), 'frequency_per_day': round(deployment_frequency, 2) }, 'incident_metrics': { 'total_incidents': total_incidents, 'resolved_incidents': resolved_incidents, 'avg_resolution_time_hours': round(avg_resolution_time, 2) } } 二、持续集成(CI)实践 2.1 Git工作流 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 # ========== Git工作流模型 ========== class GitWorkflow: """Git工作流管理""" BRANCH_STRATEGIES = { "Git Flow": { "branches": { "main": "生产分支,只接受来自release的合并", "develop": "开发主分支", "feature": "功能开发分支,从develop创建", "release": "发布准备分支,从develop创建", "hotfix": "紧急修复分支,从main创建" }, "workflow": """ 1. 从develop创建feature分支 2. 完成开发后合并回develop 3. 准备发布时创建release分支 4. 测试通过后合并到main和develop 5. 紧急修复从main创建hotfix分支 6. 修复后合并到main和develop """ }, "GitHub Flow": { "branches": { "main": "始终可部署的生产分支", "feature": "功能分支,从main创建" }, "workflow": """ 1. 从main创建feature分支 2. 提交并推送到远程 3. 创建Pull Request 4. Code Review和讨论 5. 合并到main 6. 立即部署 """ }, "GitLab Flow": { "branches": { "main": "主分支", "feature": "功能分支", "environment": "环境分支(staging, production)" }, "workflow": """ 1. 从main创建feature分支 2. 完成后合并回main 3. main分支触发CI/CD 4. 通过测试后部署到staging 5. 人工验证后部署到production """ }, "Trunk Based Development": { "branches": { "trunk/main": "主分支,所有开发在此进行" }, "workflow": """ 1. 开发者直接在主分支提交 2. 使用Feature Flags控制功能发布 3. 频繁集成到主分支 4. 持续部署 """ } } class BranchProtectionRules: """分支保护规则""" def __init__(self): self.rules = {} def add_protection(self, branch: str, rules: dict): """添加分支保护规则""" self.rules[branch] = { 'require_pull_request': rules.get('require_pull_request', True), 'require_approvals': rules.get('require_approvals', 1), 'dismiss_stale_reviews': rules.get('dismiss_stale_reviews', True), 'require_status_checks': rules.get('require_status_checks', True), 'required_status_checks': rules.get('required_status_checks', []), 'require_linear_history': rules.get('require_linear_history', True), 'allow_force_pushes': rules.get('allow_force_pushes', False), 'allow_deletions': rules.get('allow_deletions', False) } def check_protection(self, branch: str) -> dict: """检查分支保护状态""" return self.rules.get(branch, {}) 2.2 CI流水线配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 # ========== Jenkins Pipeline ========== # Jenkinsfile pipeline { agent any tools { maven 'Maven 3.8' jdk 'JDK 11' } environment { APP_NAME = 'my-application' VERSION = "${env.BUILD_ID}" REGISTRY = 'registry.example.com' IMAGE_NAME = "${REGISTRY}/${APP_NAME}:${VERSION}" } stages { stage('Checkout') { steps { checkout scm } } stage('Setup') { steps { sh ''' echo "Setting up build environment..." python3 -m venv venv . venv/bin/activate pip install -r requirements.txt ''' } } stage('Code Quality') { parallel { stage('Lint') { steps { sh ''' . venv/bin/activate flake8 src/ --max-line-length=120 ''' } } stage('Security Scan') { steps { sh ''' . venv/bin/activate bandit -r src/ ''' } } } } stage('Unit Tests') { steps { sh ''' . venv/bin/activate pytest tests/unit/ --cov=src --cov-report=xml --cov-report=html ''' } post { always { junit 'test-results/*.xml' publishHTML([ allowMissing: false, alwaysLinkToLastBuild: true, keepAll: true, reportDir: 'htmlcov', reportFiles: 'index.html', reportName: 'Coverage Report' ]) } } } stage('Integration Tests') { steps { sh ''' docker-compose -f docker-compose.test.yml up -d sleep 10 . venv/bin/activate pytest tests/integration/ docker-compose -f docker-compose.test.yml down ''' } } stage('Build') { steps { sh ''' . venv/bin/activate python setup.py sdist bdist_wheel ''' } } stage('Docker Build') { steps { script { docker.build(image: IMAGE_NAME) } } } stage('Security Scan Image') { steps { sh ''' trivy image ${IMAGE_NAME} --severity HIGH,CRITICAL ''' } } stage('Push to Registry') { when { branch 'main' } steps { script { docker.withRegistry("https://${REGISTRY}", 'docker-registry-credentials') { docker.image(IMAGE_NAME).push() docker.image(IMAGE_NAME).push('latest') } } } } stage('Deploy to Staging') { when { branch 'main' } steps { sh ''' kubectl set image deployment/${APP_NAME} \ ${APP_NAME}=${IMAGE_NAME} \ --namespace=staging kubectl rollout status deployment/${APP_NAME} \ --namespace=staging ''' } } stage('Smoke Tests') { when { branch 'main' } steps { sh ''' . venv/bin/activate pytest tests/smoke/ --base-url=https://staging.example.com ''' } } stage('Deploy to Production') { when { branch 'main' approval { input 'Deploy to Production?' } } steps { sh ''' kubectl set image deployment/${APP_NAME} \ ${APP_NAME}=${IMAGE_NAME} \ --namespace=production kubectl rollout status deployment/${APP_NAME} \ --namespace=production ''' } } } post { success { echo "Pipeline succeeded!" cleanWs() } failure { echo "Pipeline failed!" mail to: 'team@example.com', subject: "Build Failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}", body: "Check console output at ${env.BUILD_URL}" } always { sh ''' docker system prune -f ''' } } } # ========== GitHub Actions Workflow ========== # .github/workflows/ci-cd.yml name: CI/CD Pipeline on: push: branches: [main, develop] pull_request: branches: [main, develop] env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} jobs: lint: name: Lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v4 with: python-version: '3.9' cache: 'pip' - name: Install dependencies run: | python -m pip install --upgrade pip pip install flake8 black isort - name: Run flake8 run: | flake8 src/ tests/ --count --select=E9,F63,F7,F82 --show-source --statistics - name: Check code formatting run: | black --check src/ tests/ - name: Check import ordering run: | isort --check-only src/ tests/ test: name: Test runs-on: ubuntu-latest strategy: matrix: python-version: ['3.8', '3.9', '3.10', '3.11'] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} cache: 'pip' - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt pip install -r requirements-dev.txt - name: Run tests run: | pytest tests/ --cov=src --cov-report=xml --cov-report=html - name: Upload coverage uses: codecov/codecov-action@v3 with: file: ./coverage.xml flags: unittests name: codecov-umbrella security: name: Security Scan runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Run Bandit run: | pip install bandit[toml] bandit -r src/ - name: Run Safety run: | pip install safety safety check --file requirements.txt - name: Run Trivy uses: aquasecurity/trivy-action@master with: scan-type: 'fs' scan-ref: '.' format: 'sarif' output: 'trivy-results.sarif' - name: Upload Trivy results uses: github/codeql-action/upload-sarif@v2 with: sarif_file: 'trivy-results.sarif' build: name: Build Docker Image runs-on: ubuntu-latest needs: [lint, test, security] if: github.event_name == 'push' && github.ref == 'refs/heads/main' permissions: contents: read packages: write steps: - uses: actions/checkout@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - name: Log in to Container Registry uses: docker/login-action@v2 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata id: meta uses: docker/metadata-action@v4 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | type=ref,event=branch type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=sha,prefix={{branch}}- - name: Build and push uses: docker/build-push-action@v4 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max deploy: name: Deploy runs-on: ubuntu-latest needs: build if: github.event_name == 'push' && github.ref == 'refs/heads/main' environment: name: production url: https://example.com steps: - uses: actions/checkout@v3 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v2 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: us-east-1 - name: Update kubeconfig run: | aws eks update-kubeconfig --name production-cluster --region us-east-1 - name: Deploy to Kubernetes run: | kubectl set image deployment/app \ app=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \ --namespace=production - name: Verify deployment run: | kubectl rollout status deployment/app --namespace=production 2.3 测试自动化策略 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 # ========== 测试金字塔 ========== class TestAutomationStrategy: """测试自动化策略""" TEST_PYRAMID = { "Unit Tests (70%)": { "scope": "单个函数/类", "speed": "毫秒级", "cost": "低", "isolation": "完全隔离", "examples": [ "函数返回值验证", "边界条件测试", "异常处理测试" ] }, "Integration Tests (20%)": { "scope": "多个组件协作", "speed": "秒级", "cost": "中", "isolation": "部分隔离", "examples": [ "API集成测试", "数据库集成测试", "服务间通信测试" ] }, "E2E Tests (10%)": { "scope": "完整用户流程", "speed": "分钟级", "cost": "高", "isolation": "无隔离", "examples": [ "用户注册流程", "购物车完整流程", "支付完整流程" ] } } class AutomatedTestSuite: """自动化测试套件""" def __init__(self): self.unit_tests = [] self.integration_tests = [] self.e2e_tests = [] def add_unit_test(self, test_func): """添加单元测试""" self.unit_tests.append(test_func) def add_integration_test(self, test_func): """添加集成测试""" self.integration_tests.append(test_func) def add_e2e_test(self, test_func): """添加E2E测试""" self.e2e_tests.append(test_func) def run_all_tests(self): """运行所有测试""" results = { 'unit': self._run_tests(self.unit_tests), 'integration': self._run_tests(self.integration_tests), 'e2e': self._run_tests(self.e2e_tests) } return results def _run_tests(self, tests): """运行测试""" passed = 0 failed = 0 for test in tests: try: test() passed += 1 except AssertionError as e: failed += 1 print(f"Test failed: {e}") return { 'total': len(tests), 'passed': passed, 'failed': failed } # ========== 测试驱动开发(TDD)工作流 ========== class TDDWorkflow: """TDD工作流""" @staticmethod def red_green_refactor(feature_name: str): """ Red-Green-Refactor循环 Red: 编写失败的测试 Green: 编写最少代码使测试通过 Refactor: 重构代码 """ print(f"\n=== TDD Workflow for {feature_name} ===") # Red: 编写测试 print("\n[RED] Writing failing test...") test_code = f""" def test_{feature_name}(): result = {feature_name}() assert result is not None """ print(test_code) # Green: 实现功能 print("\n[GREEN] Implementing minimum code to pass...") impl_code = f""" def {feature_name}(): return True """ print(impl_code) # Refactor: 重构 print("\n[REFACTOR] Improving code quality...") print("Optimizing implementation...") return True # ========== 行为驱动开发(BDD) ========== class BDDScenario: """BDD场景定义""" def __init__(self, name: str): self.name = name self.given_steps = [] self.when_steps = [] self.then_steps = [] def given(self, description: str, action=None): """Given步骤""" self.given_steps.append({ 'description': description, 'action': action }) return self def when(self, description: str, action=None): """When步骤""" self.when_steps.append({ 'description': description, 'action': action }) return self def then(self, description: str, assertion=None): """Then步骤""" self.then_steps.append({ 'description': description, 'assertion': assertion }) return self def execute(self): """执行场景""" print(f"\nScenario: {self.name}") # 执行Given步骤 print("\nGiven:") context = {} for step in self.given_steps: print(f" {step['description']}") if step['action']: context.update(step['action'](context) or {}) # 执行When步骤 print("\nWhen:") for step in self.when_steps: print(f" {step['description']}") if step['action']: context.update(step['action'](context) or {}) # 执行Then步骤 print("\nThen:") for step in self.then_steps: print(f" {step['description']}") if step['assertion']: step['assertion'](context) return context # 使用示例 def user_login_scenario(): """用户登录场景""" scenario = BDDScenario("User successfully logs in") scenario.given("a registered user with email 'user@example.com' and password 'password123'") .when("the user submits valid credentials") .then("the user should be authenticated") .then("the user should receive an authentication token") # 实际执行时会实现具体的action和assertion return scenario 三、持续部署(CD)实践 3.1 部署策略 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 # ========== 部署策略 ========== class DeploymentStrategy: """部署策略""" @staticmethod def rolling_update(deployment_config: dict): """ 滚动更新 逐步替换旧实例,确保服务始终可用 """ total_replicas = deployment_config['replicas'] max_unavailable = deployment_config.get('max_unavailable', 1) max_surge = deployment_config.get('max_surge', 1) print(f"Starting rolling update...") print(f"Total replicas: {total_replicas}") print(f"Max unavailable: {max_unavailable}") print(f"Max surge: {max_surge}") for i in range(total_replicas): print(f"Updating replica {i+1}/{total_replicas}...") # 部署新实例 # 等待健康检查通过 # 终止旧实例 print("Rolling update completed!") @staticmethod def blue_green_deployment(deployment_config: dict): """ 蓝绿部署 并行维护两套环境,通过切换流量实现零停机部署 """ print("Starting blue-green deployment...") # 当前环境 current_env = deployment_config['current_environment'] # 'blue' or 'green' # 目标环境 target_env = 'green' if current_env == 'blue' else 'blue' print(f"Current environment: {current_env}") print(f"Target environment: {target_env}") # 1. 在目标环境部署新版本 print(f"\n1. Deploying new version to {target_env} environment...") # 部署到目标环境 # 2. 等待目标环境健康检查 print(f"\n2. Waiting for {target_env} to be healthy...") # 健康检查 # 3. 切换流量 print(f"\n3. Switching traffic from {current_env} to {target_env}...") # 切换负载均衡器 # 4. 验证新版本 print(f"\n4. Validating {target_env}...") # 运行烟雾测试 # 5. 保留旧环境(可选回滚) print(f"\n5. Keeping {current_env} as rollback option...") print("Blue-green deployment completed!") @staticmethod def canary_deployment(deployment_config: dict): """ 金丝雀部署 逐步将流量引导到新版本,监控问题并快速回滚 """ print("Starting canary deployment...") canary_steps = deployment_config.get('canary_steps', [ {'percentage': 5, 'duration': 300}, # 5% 流量,5分钟 {'percentage': 25, 'duration': 600}, # 25% 流量,10分钟 {'percentage': 50, 'duration': 600}, # 50% 流量,10分钟 {'percentage': 100, 'duration': 0} # 100% 流量 ]) for step in canary_steps: percentage = step['percentage'] duration = step['duration'] print(f"\nRouting {percentage}% of traffic to canary...") # 调整流量分配 # 监控关键指标 # 如果检测到问题,立即回滚 if duration > 0: print(f"Monitoring for {duration} seconds...") # 等待并监控 print("\nAll traffic routed to new version!") print("Canary deployment completed!") @staticmethod def a_b_testing_deployment(deployment_config: dict): """ A/B测试部署 同时运行多个版本,按特定规则分配流量 """ print("Starting A/B testing deployment...") variants = deployment_config['variants'] # [{'name': 'A', 'percentage': 50}, ...] total_percentage = sum(v['percentage'] for v in variants) if total_percentage != 100: raise ValueError(f"Total percentage must be 100%, got {total_percentage}%") print("Traffic allocation:") for variant in variants: print(f" {variant['name']}: {variant['percentage']}%") # 配置流量分配规则 # 设置指标收集 # 配置自动回滚规则 print("A/B testing deployment completed!") # ========== 部署回滚 ========== class DeploymentRollback: """部署回滚""" def __init__(self, deployment_manager): self.deployment_manager = deployment_manager self.rollback_history = [] def execute_rollback(self, target_version: str, reason: str): """执行回滚""" print(f"\nInitiating rollback to version {target_version}") print(f"Reason: {reason}") # 1. 记录回滚 rollback_record = { 'timestamp': datetime.now().isoformat(), 'from_version': self.deployment_manager.get_current_version(), 'to_version': target_version, 'reason': reason } self.rollback_history.append(rollback_record) # 2. 执行回滚 try: # 停止当前版本 print("Stopping current version...") # 部署目标版本 print(f"Deploying version {target_version}...") # 验证回滚 print("Verifying rollback...") print(f"Rollback to version {target_version} completed successfully!") except Exception as e: print(f"Rollback failed: {e}") raise def get_rollback_history(self): """获取回滚历史""" return self.rollback_history 3.2 GitOps实践 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 # ========== GitOps with ArgoCD ========== # 应用部署清单 # apps/application.yaml apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: my-application namespace: argocd spec: project: default source: repoURL: https://github.com/org/infrastructure.git targetRevision: main path: apps/my-application helm: valueFiles: - values-prod.yaml destination: server: https://kubernetes.default.svc namespace: production syncPolicy: automated: prune: true selfHeal: true syncOptions: - CreateNamespace=true # Kubernetes部署清单 # apps/my-application/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: my-application labels: app: my-application spec: replicas: 3 selector: matchLabels: app: my-application template: metadata: labels: app: my-application spec: containers: - name: app image: registry.example.com/my-application:{{ .Values.image.tag }} ports: - containerPort: 8080 resources: requests: memory: "128Mi" cpu: "100m" limits: memory: "256Mi" cpu: "200m" livenessProbe: httpGet: path: /health port: 8080 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /ready port: 8080 initialDelaySeconds: 5 periodSeconds: 5 --- apiVersion: v1 kind: Service metadata: name: my-application spec: selector: app: my-application ports: - port: 80 targetPort: 8080 type: ClusterIP --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: my-application annotations: cert-manager.io/cluster-issuer: letsencrypt-prod nginx.ingress.kubernetes.io/ssl-redirect: "true" spec: ingressClassName: nginx tls: - hosts: - app.example.com secretName: my-application-tls rules: - host: app.example.com http: paths: - path: / pathType: Prefix backend: service: name: my-application port: number: 80 # Helm Values # apps/my-application/values-prod.yaml image: tag: "1.2.3" replicas: 3 resources: requests: memory: "256Mi" cpu: "200m" limits: memory: "512Mi" cpu: "500m" autoscaling: enabled: true minReplicas: 3 maxReplicas: 10 targetCPUUtilizationPercentage: 70 targetMemoryUtilizationPercentage: 80 database: host: postgres-production.example.com port: 5432 name: app_production sslMode: require 四、基础设施即代码(IaC) 4.1 Terraform配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 # ========== Terraform配置示例 ========== # 主配置文件 # main.tf terraform { required_version = ">= 1.0" required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } backend "s3" { bucket = "terraform-state-prod" key = "production/terraform.tfstate" region = "us-east-1" encrypt = true dynamodb_table = "terraform-locks" } } provider "aws" { region = var.aws_region default_tags { tags = { Environment = var.environment Project = var.project_name ManagedBy = "Terraform" } } } # VPC配置 # modules/vpc/main.tf resource "aws_vpc" "main" { cidr_block = var.vpc_cidr enable_dns_hostnames = true enable_dns_support = true tags = { Name = "${var.project_name}-vpc" } } resource "aws_internet_gateway" "main" { vpc_id = aws_vpc.main.id tags = { Name = "${var.project_name}-igw" } } resource "aws_subnet" "public" { count = length(var.availability_zones) vpc_id = aws_vpc.main.id cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index) availability_zone = var.availability_zones[count.index] map_public_ip_on_launch = true tags = { Name = "${var.project_name}-public-${count.index}" Type = "Public" } } resource "aws_subnet" "private" { count = length(var.availability_zones) vpc_id = aws_vpc.main.id cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index + length(var.availability_zones)) availability_zone = var.availability_zones[count.index] tags = { Name = "${var.project_name}-private-${count.index}" Type = "Private" } } resource "aws_eip" "nat" { count = length(var.availability_zones) domain = "vpc" tags = { Name = "${var.project_name}-nat-${count.index}" } depends_on = [aws_internet_gateway.main] } resource "aws_nat_gateway" "main" { count = length(var.availability_zones) allocation_id = aws_eip.nat[count.index].id subnet_id = aws_subnet.public[count.index].id tags = { Name = "${var.project_name}-nat-${count.index}" } depends_on = [aws_internet_gateway.main] } # EKS集群配置 # modules/eks/main.tf resource "aws_eks_cluster" "main" { name = "${var.project_name}-cluster" role_arn = aws_iam_role.cluster.arn version = var.kubernetes_version vpc_config { subnet_ids = concat( aws_subnet.private[*].id, aws_subnet.public[*].id ) endpoint_private_access = true endpoint_public_access = true public_access_cidrs = var.allowed_public_access_cidrs } enabled_cluster_log_types = var.cluster_log_types depends_on = [ aws_iam_role_policy_attachment.cluster_amazon_eks_cluster_policy, aws_iam_role_policy_attachment.cluster_amazon_eks_vpc_resource_controller, ] tags = { Name = "${var.project_name}-eks" } } resource "aws_eks_node_group" "main" { cluster_name = aws_eks_cluster.main.name node_group_name = "${var.project_name}-node-group" node_role_arn = aws_iam_role.node.arn subnet_ids = aws_subnet.private[*].id scaling_config { desired_size = var.node_group_desired_size max_size = var.node_group_max_size min_size = var.node_group_min_size } instance_types = var.node_instance_types remote_access { ec2_ssh_key = var.ssh_key_name source_security_group_ids = [aws_security_group.node.id] } labels = { Environment = var.environment Project = var.project_name } tags = { Name = "${var.project_name}-node" } depends_on = [ aws_iam_role_policy_attachment.node_amazon_eks_worker_node_policy, aws_iam_role_policy_attachment.node_amazon_eks_cni_policy, aws_iam_role_policy_attachment.node_amazon_ec2_container_registry_read_only, ] } # 变量定义 # variables.tf variable "aws_region" { description = "AWS region" type = string default = "us-east-1" } variable "environment" { description = "Environment name" type = string validation { condition = contains(["development", "staging", "production"], var.environment) error_message = "Environment must be development, staging, or production." } } variable "project_name" { description = "Project name" type = string } variable "vpc_cidr" { description = "CIDR block for VPC" type = string default = "10.0.0.0/16" } variable "availability_zones" { description = "List of availability zones" type = list(string) default = ["us-east-1a", "us-east-1b", "us-east-1c"] } # 输出定义 # outputs.tf output "cluster_id" { description = "EKS cluster ID" value = aws_eks_cluster.main.id } output "cluster_endpoint" { description = "EKS cluster endpoint" value = aws_eks_cluster.main.endpoint } output "cluster_security_group_id" { description = "Security group ID attached to the EKS cluster" value = aws_eks_cluster.main.vpc_config[0].cluster_security_group_id } output "cluster_certificate_authority_data" { description = "Base64 encoded certificate data required to communicate with the cluster" value = aws_eks_cluster.main.certificate_authority[0].data } 4.2 Docker与Kubernetes配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 # ========== Docker配置 ========== # Dockerfile FROM python:3.11-slim # 设置工作目录 WORKDIR /app # 设置环境变量 ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ PIP_NO_CACHE_DIR=1 \ PIP_DISABLE_PIP_VERSION_CHECK=1 # 安装系统依赖 RUN apt-get update && \ apt-get install -y --no-install-recommends \ gcc \ libc6-dev \ && rm -rf /var/lib/apt/lists/* # 复制依赖文件 COPY requirements.txt . # 安装Python依赖 RUN pip install --no-cache-dir -r requirements.txt # 复制应用代码 COPY src/ ./src/ COPY config/ ./config/ # 创建非root用户 RUN useradd -m -u 1000 appuser && \ chown -R appuser:appuser /app USER appuser # 暴露端口 EXPOSE 8080 # 健康检查 HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8080/health || exit 1 # 启动应用 CMD ["gunicorn", "src.app:app", "--bind", "0.0.0.0:8080", "--workers", "4"] # docker-compose.yml version: '3.8' services: app: build: context: . dockerfile: Dockerfile image: my-application:latest ports: - "8080:8080" environment: - DATABASE_URL=postgresql://user:password@db:5432/app - REDIS_URL=redis://redis:6379 depends_on: db: condition: service_healthy redis: condition: service_started healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s restart: unless-stopped db: image: postgres:15-alpine environment: - POSTGRES_DB=app - POSTGRES_USER=user - POSTGRES_PASSWORD=password volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U user -d app"] interval: 10s timeout: 5s retries: 5 restart: unless-stopped redis: image: redis:7-alpine volumes: - redis_data:/data restart: unless-stopped nginx: image: nginx:alpine ports: - "80:80" - "443:443" volumes: - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./nginx/ssl:/etc/nginx/ssl:ro depends_on: - app restart: unless-stopped volumes: postgres_data: redis_data: # ========== Kubernetes配置 ========== # deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: my-application labels: app: my-application spec: replicas: 3 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 selector: matchLabels: app: my-application template: metadata: labels: app: my-application spec: serviceAccountName: my-application securityContext: runAsNonRoot: true runAsUser: 1000 fsGroup: 1000 containers: - name: app image: registry.example.com/my-application:1.0.0 ports: - name: http containerPort: 8080 protocol: TCP env: - name: DATABASE_URL valueFrom: secretKeyRef: name: app-secrets key: database-url - name: REDIS_URL valueFrom: configMapKeyRef: name: app-config key: redis-url resources: requests: memory: "128Mi" cpu: "100m" limits: memory: "256Mi" cpu: "200m" livenessProbe: httpGet: path: /health port: http initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 readinessProbe: httpGet: path: /ready port: http initialDelaySeconds: 5 periodSeconds: 5 timeoutSeconds: 3 failureThreshold: 3 lifecycle: preStop: exec: command: - sh - -c - sleep 15 terminationGracePeriodSeconds: 30 # service.yaml apiVersion: v1 kind: Service metadata: name: my-application spec: type: ClusterIP selector: app: my-application ports: - name: http port: 80 targetPort: http protocol: TCP # hpa.yaml apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: my-application spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: my-application minReplicas: 3 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 - type: Resource resource: name: memory target: type: Utilization averageUtilization: 80 behavior: scaleDown: stabilizationWindowSeconds: 300 policies: - type: Percent value: 50 periodSeconds: 60 scaleUp: stabilizationWindowSeconds: 0 policies: - type: Percent value: 100 periodSeconds: 30 - type: Pods value: 2 periodSeconds: 60 selectPolicy: Max 总结 构建高效的DevOps和CI/CD流水线需要: ...

现代前端工程化体系搭建:从零构建企业级前端项目架构

引言 前端工程化是提升团队开发效率和代码质量的关键。一个完善的工程化体系包括项目架构、开发规范、构建工具、测试策略、CI/CD流程等多个方面。本文将从零开始,搭建一套完整的企业级前端工程化体系。 一、项目架构设计 1.1 单体仓库(Monorepo) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 # 项目结构 my-project/ ├── packages/ │ ├── shared/ # 共享代码 │ │ ├── src/ │ │ ├── package.json │ │ └── tsconfig.json │ ├── ui/ # UI组件库 │ │ ├── src/ │ │ ├── package.json │ │ └── tsconfig.json │ ├── web/ # Web应用 │ │ ├── src/ │ │ ├── package.json │ │ └── vite.config.ts │ └── admin/ # 管理后台 │ ├── src/ │ ├── package.json │ └── vite.config.ts ├── apps/ │ └── mobile/ # 移动应用 ├── .gitignore ├── pnpm-workspace.yaml # pnpm workspace配置 ├── package.json ├── nx.json # Nx配置 └── tsconfig.base.json 1 2 3 4 # pnpm-workspace.yaml packages: - 'packages/*' - 'apps/*' 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // package.json { "name": "my-monorepo", "private": true, "scripts": { "dev": "turbo run dev", "build": "turbo run build", "test": "turbo run test", "lint": "turbo run lint", "format": "prettier --write \"**/*.{ts,tsx,json,md}\"", "changeset": "changeset", "version": "changeset version", "release": "turbo run build && changeset publish" }, "devDependencies": { "@changesets/cli": "^2.27.0", "prettier": "^3.1.0", "turbo": "^1.11.0", "typescript": "^5.3.0" } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 // turbo.json { "$schema": "https://turbo.build/schema.json", "globalDependencies": ["**/.env.*local"], "pipeline": { "build": { "dependsOn": ["^build"], "outputs": ["dist/**", ".next/**", "!.next/cache/**"] }, "dev": { "cache": false, "persistent": true }, "lint": { "outputs": [] }, "test": { "dependsOn": ["build"], "outputs": [], "inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts", "test/**/*.tsx"] } } } 1.2 分层架构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 // 分层架构设计 src/ ├── main/ # 主入口层 │ ├── index.ts │ └── App.vue ├── presentation/ # 展示层(页面、组件) │ ├── pages/ │ │ ├── home/ │ │ ├── about/ │ │ └── dashboard/ │ └── components/ │ ├── common/ │ └── features/ ├── application/ # 应用层(业务逻辑) │ ├── useCases/ │ │ ├── auth/ │ │ ├── user/ │ │ └── product/ │ └── services/ │ └── api/ ├── domain/ # 领域层(核心业务) │ ├── entities/ │ ├── valueObjects/ │ └── repositories/ ├── infrastructure/ # 基础设施层 │ ├── api/ │ ├── storage/ │ └── config/ └── shared/ # 共享代码 ├── utils/ ├── constants/ ├── types/ └── validators/ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 // 领域实体示例 // domain/entities/User.ts export class User { constructor( private readonly _id: string, private _name: string, private _email: string, private _role: UserRole ) {} get id(): string { return this._id } get name(): string { return this._name } get email(): string { return this._email } get role(): UserRole { return this._role } changeName(name: string): void { if (name.length < 2) { throw new Error('Name too short') } this._name = name } hasRole(role: UserRole): boolean { return this._role === role } } enum UserRole { ADMIN = 'ADMIN', USER = 'USER', GUEST = 'GUEST' } 二、代码规范 2.1 ESLint配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 // .eslintrc.cjs module.exports = { root: true, env: { browser: true, es2021: true, node: true }, extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:vue/vue3-recommended', 'prettier' ], parser: 'vue-eslint-parser', parserOptions: { ecmaVersion: 'latest', parser: '@typescript-eslint/parser', sourceType: 'module' }, plugins: ['@typescript-eslint', 'vue', 'import'], rules: { // TypeScript规则 '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], '@typescript-eslint/no-explicit-any': 'warn', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-non-null-assertion': 'warn', // Vue规则 'vue/multi-word-component-names': 'off', 'vue/no-v-html': 'warn', 'vue/require-default-prop': 'error', 'vue/require-prop-types': 'error', 'vue/component-name-in-template-casing': ['error', 'PascalCase'], // Import规则 'import/order': [ 'error', { groups: [ 'builtin', 'external', 'internal', 'parent', 'sibling', 'index' ], 'newlines-between': 'always', alphabetize: { order: 'asc', caseInsensitive: true } } ], 'import/no-unresolved': 'error', 'import/no-cycle': 'warn', // 通用规则 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off' }, globals: { defineProps: 'readonly', defineEmits: 'readonly', defineExpose: 'readonly', withDefaults: 'readonly' } } 2.2 Prettier配置 1 2 3 4 5 6 7 8 9 10 11 // .prettierrc { "semi": false, "singleQuote": true, "printWidth": 100, "tabWidth": 2, "trailingComma": "es5", "arrowParens": "avoid", "endOfLine": "lf", "vueIndentScriptAndStyle": false } 1 2 3 4 5 6 7 8 9 // .prettierignore node_modules dist build coverage *.min.js *.min.css package-lock.json pnpm-lock.yaml 2.3 Git提交规范 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 // .commitlintrc.cjs module.exports = { extends: ['@commitlint/config-conventional'], rules: { 'type-enum': [ 2, 'always', [ 'feat', // 新功能 'fix', // 修复bug 'docs', // 文档更新 'style', // 代码格式调整 'refactor', // 重构 'perf', // 性能优化 'test', // 测试相关 'build', // 构建系统 'ci', // CI配置 'chore', // 其他杂项 'revert' // 回退提交 ] ], 'scope-case': [2, 'always', 'kebab-case'], 'subject-case': [0], 'subject-empty': [2, 'never'], 'subject-max-length': [2, 'always', 100], 'type-empty': [2, 'never'], 'scope-empty': [1, 'never'] } } 1 2 3 4 5 # .husky/pre-commit #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" pnpm lint-staged 1 2 3 4 5 # .husky/commit-msg #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" pnpm commitlint --edit $1 1 2 3 4 5 6 7 8 9 10 11 12 // package.json { "lint-staged": { "*.{ts,tsx,vue}": [ "eslint --fix", "prettier --write" ], "*.{json,md,yml,yaml}": [ "prettier --write" ] } } 三、构建优化 3.1 Vite配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 // vite.config.ts import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import { resolve } from 'path' export default defineConfig({ plugins: [vue()], resolve: { alias: { '@': resolve(__dirname, 'src'), '@shared': resolve(__dirname, '../shared/src') } }, build: { target: 'es2015', outDir: 'dist', assetsDir: 'assets', sourcemap: false, minify: 'terser', rollupOptions: { output: { // 手动分包 manualChunks: { 'vue-vendor': ['vue', 'vue-router', 'pinia'], 'ui-library': ['element-plus'], 'utils': ['lodash-es', 'axios', 'dayjs'] }, // 文件命名 chunkFileNames: 'js/[name]-[hash].js', entryFileNames: 'js/[name]-[hash].js', assetFileNames: '[ext]/[name]-[hash].[ext]' } }, // Terser配置 terserOptions: { compress: { drop_console: true, drop_debugger: true, pure_funcs: ['console.log'] } }, // chunk大小警告阈值 chunkSizeWarningLimit: 1000 }, server: { port: 3000, host: true, open: true, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, '') } } }, // CSS配置 css: { preprocessorOptions: { scss: { additionalData: `@use "@/styles/variables" as *;` } }, modules: { localsConvention: 'camelCase' } }, // 依赖优化 optimizeDeps: { include: ['vue', 'vue-router', 'pinia', 'axios'], exclude: [] } }) 3.2 环境变量管理 1 2 3 4 5 # .env.development VITE_APP_TITLE=MyApp (Development) VITE_API_BASE_URL=http://localhost:8080/api VITE_ENABLE_MOCK=true VITE_APP_MOCK_PORT=3001 1 2 3 4 # .env.production VITE_APP_TITLE=MyApp VITE_API_BASE_URL=https://api.example.com VITE_ENABLE_MOCK=false 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // src/config/env.ts interface EnvConfig { appTitle: string apiBaseUrl: string enableMock: boolean mockPort?: number } export const env: EnvConfig = { appTitle: import.meta.env.VITE_APP_TITLE || 'MyApp', apiBaseUrl: import.meta.env.VITE_API_BASE_URL || '/api', enableMock: import.meta.env.VITE_ENABLE_MOCK === 'true', mockPort: import.meta.env.VITE_APP_MOCK_PORT ? parseInt(import.meta.env.VITE_APP_MOCK_PORT) : undefined } 四、测试策略 4.1 单元测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 // vitest.config.ts import { defineConfig } from 'vitest/config' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], test: { globals: true, environment: 'jsdom', setupFiles: ['./test/setup.ts'], coverage: { provider: 'v8', reporter: ['text', 'json', 'html'], exclude: [ 'node_modules/', 'test/', '**/*.d.ts', '**/*.config.*', '**/mockData', 'src/main.ts' ] } } }) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 // test/utils.test.ts import { describe, it, expect } from 'vitest' import { formatDate, calculateAge } from '@/utils/date' describe('Date Utils', () => { describe('formatDate', () => { it('should format date correctly', () => { const date = new Date('2025-01-01') expect(formatDate(date, 'YYYY-MM-DD')).toBe('2025-01-01') }) it('should handle invalid date', () => { expect(formatDate(null, 'YYYY-MM-DD')).toBe('') }) }) describe('calculateAge', () => { it('should calculate age correctly', () => { const birthDate = new Date('1990-01-01') const currentDate = new Date('2025-01-01') expect(calculateAge(birthDate, currentDate)).toBe(35) }) }) }) 4.2 组件测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 // test/components/Button.test.ts import { describe, it, expect } from 'vitest' import { mount } from '@vue/test-utils' import Button from '@/components/common/Button.vue' describe('Button Component', () => { it('renders properly', () => { const wrapper = mount(Button, { props: { text: 'Click me', type: 'primary' } }) expect(wrapper.text()).toContain('Click me') expect(wrapper.classes()).toContain('btn-primary') }) it('emits click event', async () => { const wrapper = mount(Button, { props: { text: 'Click me' } }) await wrapper.trigger('click') expect(wrapper.emitted('click')).toBeTruthy() }) it('disables button when loading', () => { const wrapper = mount(Button, { props: { text: 'Submit', loading: true } }) const button = wrapper.find('button') expect(button.attributes('disabled')).toBeDefined() }) }) 4.3 E2E测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 // e2e/auth.spec.ts import { test, expect } from '@playwright/test' test.describe('Authentication', () => { test.beforeEach(async ({ page }) => { await page.goto('http://localhost:3000') }) test('should login with valid credentials', async ({ page }) => { await page.click('text=Login') await page.fill('[name="email"]', 'user@example.com') await page.fill('[name="password"]', 'password123') await page.click('button[type="submit"]') await expect(page).toHaveURL(/.*dashboard/) await expect(page.locator('text=Welcome')).toBeVisible() }) test('should show error with invalid credentials', async ({ page }) => { await page.click('text=Login') await page.fill('[name="email"]', 'invalid@example.com') await page.fill('[name="password"]', 'wrongpassword') await page.click('button[type="submit"]') await expect(page.locator('text=Invalid credentials')).toBeVisible() }) }) 五、CI/CD流程 5.1 GitHub Actions配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 # .github/workflows/ci.yml name: CI on: push: branches: [main, develop] pull_request: branches: [main, develop] jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup pnpm uses: pnpm/action-setup@v2 with: version: 8 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 20 cache: 'pnpm' - name: Install dependencies run: pnpm install --frozen-lockfile - name: Run ESLint run: pnpm lint - name: Run TypeScript check run: pnpm typecheck test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup pnpm uses: pnpm/action-setup@v2 with: version: 8 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 20 cache: 'pnpm' - name: Install dependencies run: pnpm install --frozen-lockfile - name: Run unit tests run: pnpm test:unit - name: Run component tests run: pnpm test:component - name: Upload coverage uses: codecov/codecov-action@v3 with: files: ./coverage/lcov.info build: needs: [lint, test] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup pnpm uses: pnpm/action-setup@v2 with: version: 8 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 20 cache: 'pnpm' - name: Install dependencies run: pnpm install --frozen-lockfile - name: Build run: pnpm build - name: Upload build artifacts uses: actions/upload-artifact@v3 with: name: dist path: dist 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 # .github/workflows/deploy.yml name: Deploy on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup pnpm uses: pnpm/action-setup@v2 with: version: 8 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 20 cache: 'pnpm' - name: Install dependencies run: pnpm install --frozen-lockfile - name: Build run: pnpm build env: NODE_ENV: production - name: Deploy to Vercel uses: amondnet/vercel-action@v25 with: vercel-token: ${{ secrets.VERCEL_TOKEN }} vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} vercel-args: '--prod' 六、监控与日志 6.1 错误监控 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 // src/monitoring/sentry.ts import * as Sentry from '@sentry/vue' export function setupSentry(app: any) { if (import.meta.env.PROD) { Sentry.init({ app, dsn: import.meta.env.VITE_SENTRY_DSN, environment: import.meta.env.MODE, release: import.meta.env.VITE_APP_VERSION, // 性能监控 integrations: [ new Sentry.BrowserTracing({ tracePropagationTargets: ['localhost', 'example.com'] }), new Sentry.Replay() ], // 采样率 tracesSampleRate: 0.1, replaysSessionSampleRate: 0.1, replaysOnErrorSampleRate: 1.0, // 过滤敏感信息 beforeSend(event) { // 移除敏感数据 if (event.request?.headers) { delete event.request.headers['authorization'] } return event } }) } } 6.2 性能监控 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 // src/monitoring/analytics.ts export class Analytics { private queue: any[] = [] private apiKey: string constructor(apiKey: string) { this.apiKey = apiKey this.init() } private init() { // 页面加载性能 window.addEventListener('load', () => { setTimeout(() => { this.trackPageLoad() }, 0) }) // 定期发送队列数据 setInterval(() => { this.flush() }, 5000) } private trackPageLoad() { const perfData = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming this.track('page_load', { // DNS查询时间 dns: perfData.domainLookupEnd - perfData.domainLookupStart, // TCP连接时间 tcp: perfData.connectEnd - perfData.connectStart, // 请求响应时间 request: perfData.responseEnd - perfData.requestStart, // DOM解析时间 domParse: perfData.domComplete - perfData.domInteractive, // 首次绘制 firstPaint: performance.getEntriesByName('first-paint')[0]?.startTime, // 首次内容绘制 firstContentfulPaint: performance.getEntriesByName('first-contentful-paint')[0]?.startTime }) } track(eventName: string, data?: any) { this.queue.push({ event: eventName, data, timestamp: Date.now(), url: location.href, userAgent: navigator.userAgent }) } private flush() { if (this.queue.length === 0) return const data = [...this.queue] this.queue = [] fetch('/api/analytics', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': this.apiKey }, body: JSON.stringify({ events: data }), keepalive: true }).catch(err => { // 失败时重新加入队列 this.queue.unshift(...data) }) } } export const analytics = new Analytics(import.meta.env.VITE_ANALYTICS_API_KEY) 总结 前端工程化是一个系统性的工作: ...

Docker容器化完全指南:从入门到生产实践

全面掌握Docker容器化技术,从基础概念到生产环境部署的最佳实践。