高可用系统设计:构建永不宕机的后端架构

全面解析高可用系统设计的原则、模式和实战技巧,包括冗余设计、故障转移、限流降级、容错机制等,帮助你构建99.99%可用性的系统。

多智能体协作系统:构建团队协作式AI应用

深入探讨多Agent协作系统的设计架构、通信机制和协调策略,让多个AI智能体像人类团队一样高效协作完成复杂任务。

分布式事务处理:从理论到实践的完整指南

深入解析分布式事务的挑战、解决方案和最佳实践,包括2PC、3PC、Saga、TCC等模式,帮助你在微服务架构中实现数据一致性。

零信任架构:重塑网络安全的现代防护体系

全面解析零信任安全架构的设计原则、核心组件和实施路径,帮助你构建适应云原生时代的动态安全防护体系。

事件驱动架构:构建松耦合、高扩展系统的核心范式

深入解析事件驱动架构的设计原理、实现模式和技术选型,帮助你构建真正解耦、可扩展的后端系统。

AI Agent设计模式:构建智能系统的核心方法论

深入解析AI Agent的设计模式和实现方法,包括ReAct模式、工具使用、记忆系统、规划与推理,以及如何构建可靠的Agent应用。

微服务架构实战:从理论到落地的完整指南

深入探讨微服务架构的设计原则、技术选型、服务拆分策略和实战经验,帮助你构建可扩展、可维护的后端系统。

Vibe Coding:下一代编程范式的崛起与实践

探索Vibe Coding这一新兴编程范式,如何通过直觉驱动、AI辅助和流畅体验重新定义开发方式,让编程回归创造的本质。

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

引言 前端工程化是提升团队开发效率和代码质量的关键。一个完善的工程化体系包括项目架构、开发规范、构建工具、测试策略、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) 总结 前端工程化是一个系统性的工作: ...

API安全防护与鉴权机制:构建安全可靠的RESTful API

引言 API安全是现代应用开发中的核心议题。随着微服务架构的普及,API作为系统间的通信桥梁,其安全性直接影响整个系统的安全。本文将全面讲解API安全的各个方面,从认证授权到防护策略。 一、身份认证机制 1.1 JWT(JSON Web Token) 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 // JWT服务实现 import jwt from 'jsonwebtoken' import bcrypt from 'bcrypt' interface JWTPayload { userId: string email: string role: string } class AuthService { private readonly JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key' private readonly JWT_REFRESH_SECRET = process.env.JWT_REFRESH_SECRET || 'your-refresh-secret' private readonly JWT_EXPIRES_IN = '15m' private readonly JWT_REFRESH_EXPIRES_IN = '7d' // 生成访问令牌 generateAccessToken(payload: JWTPayload): string { return jwt.sign(payload, this.JWT_SECRET, { expiresIn: this.JWT_EXPIRES_IN, issuer: 'myapp.com', audience: 'myapp-api' }) } // 生成刷新令牌 generateRefreshToken(payload: JWTPayload): string { return jwt.sign( { userId: payload.userId }, this.JWT_REFRESH_SECRET, { expiresIn: this.JWT_REFRESH_EXPIRES_IN } ) } // 验证访问令牌 verifyAccessToken(token: string): JWTPayload | null { try { return jwt.verify(token, this.JWT_SECRET) as JWTPayload } catch (error) { return null } } // 验证刷新令牌 verifyRefreshToken(token: string): { userId: string } | null { try { return jwt.verify(token, this.JWT_REFRESH_SECRET) as { userId: string } } catch (error) { return null } } // 密码哈希 async hashPassword(password: string): Promise<string> { return bcrypt.hash(password, 12) } // 密码验证 async comparePassword(password: string, hash: string): Promise<boolean> { return bcrypt.compare(password, hash) } // 登录流程 async login(email: string, password: string) { const user = await this.findUserByEmail(email) if (!user) { throw new Error('User not found') } const isValid = await this.comparePassword(password, user.passwordHash) if (!isValid) { throw new Error('Invalid password') } const payload: JWTPayload = { userId: user.id, email: user.email, role: user.role } return { accessToken: this.generateAccessToken(payload), refreshToken: this.generateRefreshToken(payload), expiresIn: 15 * 60 // 15分钟 } } // 刷新令牌 async refreshTokens(refreshToken: string) { const payload = this.verifyRefreshToken(refreshToken) if (!payload) { throw new Error('Invalid refresh token') } const user = await this.findUserById(payload.userId) if (!user) { throw new Error('User not found') } const jwtPayload: JWTPayload = { userId: user.id, email: user.email, role: user.role } return { accessToken: this.generateAccessToken(jwtPayload), refreshToken: this.generateRefreshToken(jwtPayload) } } private async findUserByEmail(email: string) { // 数据库查询实现 return null } private async findUserById(userId: string) { // 数据库查询实现 return null } } export const authService = new AuthService() 1.2 Express中间件 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 // 认证中间件 import { Request, Response, NextFunction } from 'express' declare global { namespace Express { interface Request { user?: { userId: string email: string role: string } } } } // JWT认证中间件 export function authenticate(req: Request, res: Response, next: NextFunction) { const authHeader = req.headers.authorization if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ error: 'Unauthorized', message: 'Missing or invalid authorization header' }) } const token = authHeader.substring(7) const payload = authService.verifyAccessToken(token) if (!payload) { return res.status(401).json({ error: 'Unauthorized', message: 'Invalid or expired token' }) } req.user = payload next() } // 角色授权中间件 export function authorize(...roles: string[]) { return (req: Request, res: Response, next: NextFunction) => { if (!req.user) { return res.status(401).json({ error: 'Unauthorized', message: 'Authentication required' }) } if (!roles.includes(req.user.role)) { return res.status(403).json({ error: 'Forbidden', message: 'Insufficient permissions' }) } next() } } // 可选认证中间件(允许未登录用户访问) export function optionalAuthenticate(req: Request, res: Response, next: NextFunction) { const authHeader = req.headers.authorization if (authHeader && authHeader.startsWith('Bearer ')) { const token = authHeader.substring(7) const payload = authService.verifyAccessToken(token) if (payload) { req.user = payload } } next() } // 使用示例 import express from 'express' const router = express.Router() // 公开路由 router.get('/public', (req, res) => { res.json({ message: 'Public endpoint' }) }) // 需要认证 router.get('/profile', authenticate, (req, res) => { res.json({ user: req.user }) }) // 需要特定角色 router.delete('/users/:id', authenticate, authorize('admin'), (req, res) => { res.json({ message: 'User deleted' }) }) // 可选认证(登录和未登录都可以访问) router.get('/content', optionalAuthenticate, (req, res) => { if (req.user) { res.json({ content: 'premium content', user: req.user }) } else { res.json({ content: 'free content' }) } }) 1.3 OAuth 2.0实现 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 // OAuth 2.0授权码模式 import crypto from 'crypto' import { OAuth2Client } from 'google-auth-library' class OAuthService { private clients = new Map() private authorizationCodes = new Map() private accessTokens = new Map() // 注册OAuth客户端 registerClient(clientId: string, redirectUris: string[], scopes: string[]) { const clientSecret = crypto.randomBytes(32).toString('hex') this.clients.set(clientId, { clientId, clientSecret, redirectUris, scopes }) return { clientId, clientSecret } } // 生成授权码 generateAuthorizationCode(userId: string, clientId: string, scopes: string[]): string { const code = crypto.randomBytes(32).toString('hex') this.authorizationCodes.set(code, { userId, clientId, scopes, expiresAt: Date.now() + 10 * 60 * 1000 // 10分钟过期 }) return code } // 验证授权码并生成访问令牌 async exchangeCodeForToken(code: string, clientId: string, clientSecret: string, redirectUri: string) { const authCode = this.authorizationCodes.get(code) if (!authCode) { throw new Error('Invalid authorization code') } if (authCode.clientId !== clientId) { throw new Error('Client ID mismatch') } if (Date.now() > authCode.expiresAt) { this.authorizationCodes.delete(code) throw new Error('Authorization code expired') } const client = this.clients.get(clientId) if (client.clientSecret !== clientSecret) { throw new Error('Invalid client secret') } if (!client.redirectUris.includes(redirectUri)) { throw new Error('Invalid redirect URI') } // 生成访问令牌 const accessToken = crypto.randomBytes(32).toString('hex') this.accessTokens.set(accessToken, { userId: authCode.userId, clientId, scopes: authCode.scopes, expiresAt: Date.now() + 60 * 60 * 1000 // 1小时过期 }) // 删除已使用的授权码 this.authorizationCodes.delete(code) return { access_token: accessToken, token_type: 'Bearer', expires_in: 3600, scope: authCode.scopes.join(' ') } } // 验证访问令牌 verifyAccessToken(accessToken: string) { const token = this.accessTokens.get(accessToken) if (!token) { return null } if (Date.now() > token.expiresAt) { this.accessTokens.delete(accessToken) return null } return token } // Google OAuth集成 async verifyGoogleToken(idToken: string) { const client = new OAuth2Client(process.env.GOOGLE_CLIENT_ID) const ticket = await client.verifyIdToken({ idToken, audience: process.env.GOOGLE_CLIENT_ID }) const payload = ticket.getPayload() return { userId: payload.sub, email: payload.email, name: payload.name, picture: payload.picture } } } export const oauthService = new OAuthService() 二、数据安全 2.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 // 加密服务 import crypto from 'crypto' class EncryptionService { private readonly ALGORITHM = 'aes-256-gcm' private readonly KEY_LENGTH = 32 private readonly IV_LENGTH = 16 private readonly AUTH_TAG_LENGTH = 16 private key: Buffer constructor() { // 从环境变量获取加密密钥 const keyString = process.env.ENCRYPTION_KEY || 'default-key-change-in-production' this.key = crypto.scryptSync(keyString, 'salt', this.KEY_LENGTH) } // 加密数据 encrypt(plaintext: string): string { const iv = crypto.randomBytes(this.IV_LENGTH) const cipher = crypto.createCipheriv(this.ALGORITHM, this.key, iv) let encrypted = cipher.update(plaintext, 'utf8', 'hex') encrypted += cipher.final('hex') const authTag = cipher.getAuthTag() // 组合:iv + authTag + encrypted return iv.toString('hex') + authTag.toString('hex') + encrypted } // 解密数据 decrypt(ciphertext: string): string { const iv = Buffer.from(ciphertext.slice(0, this.IV_LENGTH * 2), 'hex') const authTag = Buffer.from( ciphertext.slice(this.IV_LENGTH * 2, (this.IV_LENGTH + this.AUTH_TAG_LENGTH) * 2), 'hex' ) const encrypted = ciphertext.slice((this.IV_LENGTH + this.AUTH_TAG_LENGTH) * 2) const decipher = crypto.createDecipheriv(this.ALGORITHM, this.key, iv) decipher.setAuthTag(authTag) let decrypted = decipher.update(encrypted, 'hex', 'utf8') decrypted += decipher.final('utf8') return decrypted } // 哈希数据(单向) hash(data: string): string { return crypto.createHash('sha256').update(data).digest('hex') } // 生成HMAC generateHMAC(data: string, secret: string): string { return crypto.createHmac('sha256', secret).update(data).digest('hex') } // 验证HMAC verifyHMAC(data: string, hmac: string, secret: string): boolean { const computedHMAC = this.generateHMAC(data, secret) return crypto.timingSafeEqual( Buffer.from(computedHMAC, 'hex'), Buffer.from(hmac, 'hex') ) } } export const encryptionService = new EncryptionService() // 使用示例 const sensitiveData = 'user-ssn-123-45-6789' const encrypted = encryptionService.encrypt(sensitiveData) const decrypted = encryptionService.decrypt(encrypted) // 数据库中存储加密数据 async function saveUserWithEncryptedData(userData: any) { const encrypted = encryptionService.encrypt(userData.ssn) await db.users.create({ ...userData, ssn: encrypted, ssnHash: encryptionService.hash(userData.ssn) // 用于查询 }) } 2.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 // Webhook签名验证 import crypto from 'crypto' import { Request, Response } from 'express' function verifyWebhookSignature(req: Request, res: Response, next: NextFunction) { const signature = req.headers['x-webhook-signature'] as string const timestamp = req.headers['x-webhook-timestamp'] as string if (!signature || !timestamp) { return res.status(401).json({ error: 'Missing signature headers' }) } // 检查时间戳(防重放攻击) const now = Date.now() const webhookTime = parseInt(timestamp) if (Math.abs(now - webhookTime) > 5 * 60 * 1000) { // 5分钟窗口 return res.status(401).json({ error: 'Request too old' }) } // 生成预期签名 const payload = `${timestamp}.${req.body}` const expectedSignature = crypto .createHmac('sha256', process.env.WEBHOOK_SECRET!) .update(payload) .digest('hex') // 安全比较签名 const isValid = crypto.timingSafeEqual( Buffer.from(signature, 'hex'), Buffer.from(expectedSignature, 'hex') ) if (!isValid) { return res.status(401).json({ error: 'Invalid signature' }) } next() } 三、防护策略 3.1 SQL注入防护 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 // 使用参数化查询 import { Pool } from 'pg' class UserRepository { private pool: Pool constructor(pool: Pool) { this.pool = pool } // 安全的查询 async findById(id: string) { const query = 'SELECT * FROM users WHERE id = $1' const result = await this.pool.query(query, [id]) return result.rows[0] } async findByEmail(email: string) { const query = 'SELECT * FROM users WHERE email = $1' const result = await this.pool.query(query, [email]) return result.rows[0] } // 使用查询构建器(如Knex.js) async findWithFilters(filters: any) { const query = this.pool .select('*') .from('users') if (filters.email) { query = query.where('email', filters.email) } if (filters.role) { query = query.where('role', filters.role) } if (filters.minAge) { query = query.where('age', '>=', filters.minAge) } return await query } // 使用ORM(如Prisma、TypeORM) async create(data: any) { // ORM自动处理参数化查询 return await this.pool.query( 'INSERT INTO users (name, email, password_hash) VALUES ($1, $2, $3) RETURNING *', [data.name, data.email, data.passwordHash] ) } } 3.2 XSS防护 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 // 输入验证和输出编码 import validator from 'validator' import xss from 'xss' class SecurityMiddleware { // 输入验证 static validateInput(req: Request, res: Response, next: NextFunction) { const { name, email, bio } = req.body // 验证和清理输入 if (name && !validator.isLength(name, { min: 1, max: 100 })) { return res.status(400).json({ error: 'Invalid name length' }) } if (email && !validator.isEmail(email)) { return res.status(400).json({ error: 'Invalid email format' }) } if (bio && !validator.isLength(bio, { max: 500 })) { return res.status(400).json({ error: 'Bio too long' }) } // 清理XSS if (name) req.body.name = xss(name) if (bio) req.body.bio = xss(bio) next() } // 设置CSP头 static setSecurityHeaders(req: Request, res: Response, next: NextFunction) { // 内容安全策略 res.setHeader('Content-Security-Policy', "default-src 'self'; " + "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.example.com; " + "style-src 'self' 'unsafe-inline'; " + "img-src 'self' data: https:; " + "font-src 'self'; " + "connect-src 'self' https://api.example.com; " + "frame-ancestors 'none';" ) // 其他安全头 res.setHeader('X-Content-Type-Options', 'nosniff') res.setHeader('X-Frame-Options', 'DENY') res.setHeader('X-XSS-Protection', '1; mode=block') res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin') res.setHeader('Permissions-Policy', 'geolocation=(), microphone=(), camera=()') next() } } 3.3 CORS配置 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 // CORS配置 import express from 'express' import cors from 'cors' const app = express() // 生产环境CORS配置 const corsOptions = { origin: function (origin: string | undefined, callback: Function) { const allowedOrigins = [ 'https://example.com', 'https://www.example.com', 'https://app.example.com' ] // 允许无origin的请求(如移动应用、Postman) if (!origin) return callback(null, true) if (allowedOrigins.indexOf(origin) !== -1) { callback(null, true) } else { callback(new Error('Not allowed by CORS')) } }, credentials: true, // 允许携带cookie methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization'], exposedHeaders: ['X-Total-Count'], maxAge: 86400 // 预检请求缓存24小时 } app.use(cors(corsOptions)) // 或者针对特定路由 app.options('/api/*', cors(corsOptions)) app.get('/api/data', cors(corsOptions), (req, res) => { res.json({ data: 'sensitive data' }) }) 3.4 速率限制 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 // API速率限制 import rateLimit from 'express-rate-limit' import RedisStore from 'rate-limit-redis' import Redis from 'ioredis' // 不同场景的速率限制 // 1. 通用API限制 const generalLimiter = rateLimit({ store: new RedisStore({ client: new Redis(process.env.REDIS_URL) }), windowMs: 15 * 60 * 1000, // 15分钟 max: 100, // 限制100次请求 standardHeaders: true, legacyHeaders: false, message: 'Too many requests from this IP, please try again later.', handler: (req, res) => { res.status(429).json({ error: 'Too many requests', message: 'Rate limit exceeded, please try again later.', retryAfter: 900 // 秒 }) } }) // 2. 登录限流(更严格) const loginLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 5, // 15分钟内最多5次登录尝试 skipSuccessfulRequests: true, // 成功的请求不计入限制 message: 'Too many login attempts, please try again later.' }) // 3. API密钥限流 const apiKeyLimiter = rateLimit({ store: new RedisStore({ client: new Redis(process.env.REDIS_URL), prefix: 'limiter:apikey:' }), windowMs: 60 * 1000, // 1分钟 max: 60, // 每分钟60次 keyGenerator: (req) => { return req.headers['x-api-key'] as string } }) // 应用限流 app.use('/api/', generalLimiter) app.post('/api/auth/login', loginLimiter) app.use('/api/v2/', apiKeyLimiter) // 基于用户的限流 async function getUserRateLimit(userId: string) { const user = await db.users.findById(userId) // 不同用户等级有不同限制 const limits = { free: { windowMs: 60 * 1000, max: 10 }, pro: { windowMs: 60 * 1000, max: 100 }, enterprise: { windowMs: 60 * 1000, max: 1000 } } return limits[user.plan] || limits.free } 四、安全最佳实践 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 // helmet安全头配置 import helmet from 'helmet' app.use(helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'"], scriptSrc: ["'self'"], imgSrc: ["'self'", "data:", "https:"], } }, hsts: { maxAge: 31536000, includeSubDomains: true, preload: true }, noSniff: true, xssFilter: true, frameguard: { action: 'deny' } })) // 禁用不必要的头 app.disable('x-powered-by') 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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 // 安全审计日志 class SecurityAuditLogger { private auditLog: any constructor(auditLog: any) { this.auditLog = auditLog } logAuthenticationAttempt(userId: string, success: boolean, ip: string) { this.auditLog.create({ eventType: 'AUTH_ATTEMPT', userId, success, ip, timestamp: new Date(), userAgent: undefined }) } logAuthorizationAttempt(userId: string, resource: string, action: string, success: boolean) { this.auditLog.create({ eventType: 'AUTHZ_ATTEMPT', userId, resource, action, success, timestamp: new Date() }) } logDataAccess(userId: string, resourceType: string, resourceId: string) { this.auditLog.create({ eventType: 'DATA_ACCESS', userId, resourceType, resourceId, timestamp: new Date() }) } logSecurityEvent(eventType: string, details: any) { this.auditLog.create({ eventType, details, timestamp: new Date() }) } } // 使用示例 const securityLogger = new SecurityAuditLogger(auditLog) app.post('/api/auth/login', async (req, res) => { const { email, password } = req.body const ip = req.ip try { const result = await authService.login(email, password) securityLogger.logAuthenticationAttempt(result.user.id, true, ip) res.json(result) } catch (error) { securityLogger.logAuthenticationAttempt(email, false, ip) res.status(401).json({ error: 'Invalid credentials' }) } }) 4.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 // 使用Joi进行输入验证 import Joi from 'joi' // 验证schemas const schemas = { register: Joi.object({ name: Joi.string().min(2).max(50).required(), email: Joi.string().email().required(), password: Joi.string() .min(8) .pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/) .required() .messages({ 'string.pattern.base': 'Password must contain uppercase, lowercase, number, and special character' }), confirmPassword: Joi.string().valid(Joi.ref('password')).required(), age: Joi.number().integer().min(13).max(120) }), createPost: Joi.object({ title: Joi.string().min(5).max(200).required(), content: Joi.string().min(10).max(10000).required(), tags: Joi.array().items(Joi.string().max(30)).max(10), published: Joi.boolean().default(false) }), updateProfile: Joi.object({ name: Joi.string().min(2).max(50), bio: Joi.string().max(500), website: Joi.string().uri(), avatar: Joi.string().uri() }) } // 验证中间件 function validate(schemaName: keyof typeof schemas) { return (req: Request, res: Response, next: NextFunction) => { const { error, value } = schemas[schemaName].validate(req.body, { abortEarly: false, stripUnknown: true }) if (error) { const errors = error.details.map(detail => ({ field: detail.path.join('.'), message: detail.message })) return res.status(400).json({ errors }) } req.body = value next() } } // 使用 app.post('/api/auth/register', validate('register'), authController.register) app.post('/api/posts', authenticate, validate('createPost'), postController.create) 总结 API安全是一个多层次的主题: ...