性能优化与安全
让应用又快又安全——全栈开发者的最后两把刷子
🎯 学习目标
学完本章,你将能够:
- 使用 Redis 缓存和数据库连接池优化后端性能
- 理解前端性能优化策略:懒加载、代码分割和图片优化
- 防御常见安全威胁:XSS、CSRF、SQL 注入和速率攻击
- 配置 CORS、CSP 等安全响应头
- 安全管理和轮换密钥与依赖审计
你有没有想过
为什么接口响应从 50ms 变成了 500ms?为什么 Google 搜索你的网站会出现"此网站已被黑客入侵"的警告?
性能和安全是生产环境的两条生命线。慢一秒,用户跑一半;漏一个洞,数据全泄露。
这一章是全课程的最后一章,也是最不能跳过的一章。我们从后端缓存、前端优化,讲到四大安全漏洞的防御。
Part 1: 后端性能
缓存策略和数据库索引,数量级的性能提升。
Redis 缓存
不重复计算,直接返回结果
缓存就是把计算结果存起来,下次同样的请求直接返回,不再重复计算。
// 通用缓存包装器
async function cacheable<T>(
key: string,
fetcher: () => Promise<T>,
ttl: number = 300 // 默认5分钟
): Promise<T> {
const cached = await redis.get(key);
if (cached) return JSON.parse(cached);
const data = await fetcher();
await redis.setex(key, ttl, JSON.stringify(data));
return data;
}
// 使用
const user = await cacheable(
`user:${id}`,
() => prisma.user.findUnique({ where: { id } }),
600 // 10分钟
);
这个比喻只是为了帮你建立直观感受:缓存像把常用工具放在桌面,而不是每次都去仓库找。实际上缓存涉及失效策略、穿透防护等复杂机制。
缓存失效策略
数据改了,缓存要跟着变
// 写操作后使缓存失效
app.put('/api/users/:id', async (req, res) => {
const user = await prisma.user.update({
where: { id: Number(req.params.id) },
data: req.body,
});
// 使相关缓存失效
await redis.del(`user:${req.params.id}`);
await redis.del('users:list');
res.json(user);
});
| 模式 | 策略 | 适用场景 |
|---|---|---|
| Cache-Aside | 先查缓存,miss 时查 DB 回填 | 通用 |
| Write-Through | 写数据时同时更新缓存 | 一致性要求高 |
| Write-Behind | 先写缓存,异步写 DB | 写密集场景 |
数据库连接池
避免每次请求都重建连接
建立数据库连接是一个昂贵的操作——TCP 握手、TLS 协商、权限校验,单次可能要 50-200ms。连接池在应用启动时预先创建一组连接,请求到来时直接复用。
// Prisma 内置连接池,通过 URL 参数配置
// .env
DATABASE_URL="postgresql://user:pass@localhost:5432/mydb?connection_limit=20&pool_timeout=30"
| 参数 | 作用 | 建议值 |
|---|---|---|
| connection_limit | 最大连接数 | CPU 核心数 x 2 + 1 |
| pool_timeout | 获取连接等待时间 | 10-30s |
Prisma 默认连接池大小为 num_physical_cpus * 2 + 1。在 Serverless 环境中,由于实例频繁冷启动,建议使用 Prisma Data Proxy 或 PgBouncer 等外部池化方案。
连接池监控
// 监控连接池状态(Prisma 内置指标)
const metrics = await prisma.$metrics.json();
console.log(metrics.counters);
// 告警阈值:活跃连接 > 80% 最大限制时需扩容
// 告警阈值:等待请求数 > 0 时排查慢查询
| 指标 | 健康范围 | 异常处理 |
|---|---|---|
| active connections | < 80% of limit | 增加 pool_size |
| waiting requests | 0 | 优化慢查询 / 加 pool |
| avg acquire time | < 50ms | 检查网络 / 连接泄漏 |
数据库索引
给查询加速的目录
// prisma/schema.prisma
model Post {
id Int @id @default(autoincrement())
title String
slug String @unique
published Boolean @default(false)
createdAt DateTime @default(now())
authorId Int
author User @relation(fields: [authorId], references: [id])
@@index([authorId]) // 外键索引
@@index([published, createdAt]) // 复合索引
}
复合索引有最左前缀原则:索引 [published, createdAt] 能优化 WHERE published = true,但不能优化 WHERE createdAt > '2024-01-01'(跳过了第一列)。
N+1 问题
最常见的数据库性能杀手
// 错误:N+1 查询
const posts = await prisma.post.findMany(); // 1次
for (const post of posts) {
const author = await prisma.user.findUnique({ // N次!
where: { id: post.authorId },
});
}
// 正确:使用 include 预加载
const posts = await prisma.post.findMany({
include: { author: true }, // 1次 JOIN 搞定
});
// 更好:只查需要的字段
const posts = await prisma.post.findMany({
select: {
id: true, title: true,
author: { select: { id: true, name: true } },
},
});
Part 2: 前端性能
代码分割、懒加载、图片优化。
组件懒加载
按需加载,减少首屏体积
首屏只需要展示核心内容,图表、地图、富文本编辑器等重组件可以等用户真正需要时再下载。
// React.lazy + Suspense
import { lazy, Suspense } from 'react';
const HeavyChart = lazy(() => import('@/components/HeavyChart'));
const RichEditor = lazy(() => import('@/components/RichEditor'));
function AnalyticsPage() {
return (
<div>
<h1>数据分析</h1>
<Suspense fallback={<div>加载中...</div>}>
<HeavyChart />
</Suspense>
</div>
);
}
// Vue 3 defineAsyncComponent
import { defineAsyncComponent } from 'vue';
const AsyncMap = defineAsyncComponent(() =>
import('@/components/Map.vue')
);
// 路由级别代码分割(React Router v6)
import { lazy } from 'react';
import { createBrowserRouter } from 'react-router-dom';
const Settings = lazy(() => import('./pages/Settings'));
const Profile = lazy(() => import('./pages/Profile'));
const router = createBrowserRouter([
{ path: '/settings', element: <Settings /> },
{ path: '/profile', element: <Profile /> },
]);
压缩与 HTTP 缓存
减小体积 + 避免重复传输
Gzip 或 Brotli 压缩可以将文本资源体积减少 60%-80%。HTTP 缓存让浏览器在资源未变更时直接使用本地副本。
// Express 压缩中间件
import compression from 'compression';
app.use(compression({
level: 6, // 压缩级别 1-9
threshold: 1024, // 大于 1KB 才压缩
filter: (req, res) => {
if (req.headers['x-no-compression']) return false;
return compression.filter(req, res);
},
}));
// 静态资源长期缓存(带 hash 文件名)
app.use('/static', express.static('public', {
maxAge: '1y', // 缓存 1 年
immutable: true, // 文件内容不变
}));
// API 响应短期缓存
app.get('/api/config', (req, res) => {
res.set('Cache-Control', 'public, max-age=300'); // 5 分钟
res.json(config);
});
// 资源变更时协商缓存
res.set({
'ETag': '"33a64df5"',
'Last-Modified': 'Thu, 01 Jul 2026 00:00:00 GMT',
});
| 策略 | 响应头 | 适用场景 |
|---|---|---|
| 强缓存 | Cache-Control: max-age=31536000 | 带 hash 的静态资源 |
| 协商缓存 | ETag / If-None-Match | API 响应、HTML |
| 不缓存 | Cache-Control: no-store | 敏感数据、实时信息 |
Brotli 压缩
Brotli 比 Gzip 再压缩 15%-25%,但压缩速度慢,适合预压缩静态资源。
import shrinkRay from 'shrink-ray-current';
app.use(shrinkRay()); // 优先使用 Brotli,降级到 Gzip
代码分割
不用的代码不加载
// React.lazy 懒加载重型组件
import { lazy, Suspense } from 'react';
const HeavyChart = lazy(() => import('@/components/HeavyChart'));
function DashboardPage() {
return (
<div>
<h1>仪表盘</h1>
<Suspense fallback={<div>加载中...</div>}>
<HeavyChart />
</Suspense>
</div>
);
}
// Next.js dynamic(支持 SSR 控制)
import dynamic from 'next/dynamic';
const Map = dynamic(() => import('@/components/Map'), {
ssr: false, // 禁用 SSR
loading: () => <div>地图加载中...</div>,
});
图片优化
Next.js Image 自动处理格式和尺寸
import Image from 'next/image';
// 自动转 WebP/AVIF、适配尺寸、懒加载
<Image
src="/hero.jpg"
alt="首页大图"
width={1200}
height={600}
sizes="(max-width: 768px) 100vw, 50vw"
priority // 首屏图片优先加载
/>
| 指标 | 衡量内容 | 优秀阈值 |
|---|---|---|
| LCP | 最大内容渲染时间 | < 2.5s |
| FID | 首次交互延迟 | < 100ms |
| CLS | 累积布局偏移 | < 0.1 |
| TTFB | 首字节时间 | < 800ms |
前端性能监控
用数据驱动性能优化
优化前先测量。Web Vitals 是 Google 提出的核心指标,帮助开发者从用户视角评估页面体验。
// web-vitals 库上报核心指标
import { onCLS, onINP, onLCP } from 'web-vitals';
function report(metric) {
navigator.sendBeacon('/analytics', JSON.stringify({
name: metric.name, // CLS / INP / LCP
value: metric.value, // 毫秒或分数
rating: metric.rating, // good / needs-improvement / poor
}));
}
onCLS(report); // 累积布局偏移
onINP(report); // 交互到下一次绘制
onLCP(report); // 最大内容渲染
| 指标 | 含义 | 优秀 | 需改进 |
|---|---|---|---|
| LCP | 最大内容渲染时间 | < 2.5s | 2.5-4s |
| INP | 交互到下一次绘制 | < 200ms | 200-500ms |
| CLS | 累积布局偏移 | < 0.1 | 0.1-0.25 |
建议将性能数据接入 CI,每次部署自动对比基线,性能回退 > 10% 时触发告警。
Part 3: 安全防御
XSS、CSRF、SQL 注入、依赖审计——四大安全威胁。
Part 3: 请求安全
速率限制、跨域控制、内容安全策略——防御恶意请求。
速率限制深入
滑动窗口 vs 固定窗口,分布式计数
简单的每分钟 100 次限制可能被窗口边界突刺攻击绕过——在两个窗口的交界点各发 100 次,实际 200 次只在 1 秒内完成。
// express-rate-limit:完整配置
import rateLimit from 'express-rate-limit';
// 反向代理(Nginx / ELB / Cloudflare)后必须设置 trust proxy,
// 否则 req.ip 永远是代理回环地址(如 127.0.0.1),限流会退化成"全站共享一个桶"。
// 设为代理层数(通常是 1),不要设为 true(会有 IP 欺骗风险)。
app.set('trust proxy', 1);
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 分钟
limit: 100, // 每个 IP 最大请求数
standardHeaders: 'draft-7', // 返回 RateLimit-* 头
legacyHeaders: false, // 禁用 X-RateLimit-* 头
message: { error: '请求过于频繁,请 15 分钟后再试' },
// 用默认 keyGenerator 即可,它会读取上面 trust proxy 解析后的 req.ip。
// 不要自己读 req.headers['x-forwarded-for']——该头部可被客户端任意伪造,攻击者能借此绕过限流。
skip: (req) => req.path === '/health', // 健康检查不限速
});
app.use('/api/', apiLimiter);
trust proxy,导致所有请求的 req.ip 都是 127.0.0.1,限流对全站失效;② 自己读 x-forwarded-for 头当 key,而该头可被任意伪造,攻击者换个 IP 字符串就能绕过限流。正确做法是设置 trust proxy 后用默认 keyGenerator,让 Express 安全地解析真实客户端 IP。
| 算法 | 优点 | 缺点 |
|---|---|---|
| 固定窗口 | 实现简单,内存少 | 边界突刺问题 |
| 滑动窗口 | 平滑限流,无突刺 | 内存稍多,实现复杂 |
| 令牌桶 | 允许合理突发流量 | 参数调优复杂 |
// 通过 RateLimit 标准化头返回配额信息
app.get('/api/data', (req, res) => {
// 响应头:RateLimit-Limit: 100, RateLimit-Remaining: 95
res.json({ ok: true });
});
// 分布式环境:使用 Redis 存储计数
import { RedisStore } from 'rate-limit-redis';
import { createClient } from 'redis';
const client = createClient({ url: process.env.REDIS_URL });
await client.connect();
const limiter = rateLimit({
store: new RedisStore({
sendCommand: (...args: string[]) => client.sendCommand(args),
}),
windowMs: 60 * 1000,
limit: 100,
});
CORS 跨域资源共享
控制哪些来源可以访问你的 API
同源策略是浏览器安全的基础——默认情况下,网页只能请求同协议、同域名、同端口的资源。CORS(跨域资源共享)是允许特定来源访问的机制。
// 基础配置:允许特定来源
import cors from 'cors';
app.use(cors({
origin: ['https://myapp.com', 'https://admin.myapp.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true, // 允许携带 Cookie
maxAge: 86400, // 预检请求缓存 24 小时
}));
credentials: true 时,不能使用 origin: '*'。浏览器会拒绝带通配符 origin + 凭证的组合。必须显式指定允许的来源列表。
// 动态来源验证(多租户场景)
const allowedDomains = ['.myapp.com', 'localhost:3000'];
app.use(cors({
origin: (origin, callback) => {
// 允许无 origin 的请求(如移动端、Postman)
if (!origin) return callback(null, true);
const isAllowed = allowedDomains.some(domain =>
origin === domain || origin.endsWith(domain)
);
if (isAllowed) {
callback(null, true);
} else {
callback(new Error('Origin ' + origin + ' not allowed by CORS'));
}
},
credentials: true,
}));
CSP 内容安全策略
告诉浏览器哪些资源可以加载和执行
Content-Security-Policy 响应头是一个深度防御层——即使攻击者成功注入了恶意脚本,CSP 也能阻止其执行。
// 使用 Helmet 配置 CSP
import helmet from 'helmet';
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"], // 默认只允许同源
scriptSrc: ["'self'", "'nonce-abc123'"], // 脚本仅同源 + nonce
styleSrc: ["'self'", "'unsafe-inline'"], // 样式允许内联
imgSrc: ["'self'", "data:", "https://cdn.myapp.com"],
connectSrc: ["'self'"], // fetch/XHR 仅同源
fontSrc: ["'self'", "https://fonts.googleapis.com"],
objectSrc: ["'none'"], // 禁止 Flash 等插件
baseUri: ["'self'"], // 限制 <base> 标签
formAction: ["'self'"], // 表单提交仅同源
frameAncestors: ["'none'"], // 禁止被 iframe 嵌入
},
},
// 其他安全头也一并启用
crossOriginEmbedderPolicy: true,
crossOriginOpenerPolicy: true,
crossOriginResourcePolicy: { policy: "same-origin" },
}));
// 对应生成的 HTTP 响应头
// Content-Security-Policy: default-src 'self';
// script-src 'self' 'nonce-abc123';
// style-src 'self' 'unsafe-inline';
// img-src 'self' data: https://cdn.myapp.com;
// connect-src 'self';
// font-src 'self' https://fonts.googleapis.com;
// object-src 'none';
// base-uri 'self';
// form-action 'self';
// frame-ancestors 'none'
Nonce 与 Hash 白名单
如果页面需要内联脚本,可以使用 nonce 或 hash 白名单放行特定内联代码。
import { randomBytes } from 'crypto';
app.use((req, res, next) => {
res.locals.nonce = randomBytes(16).toString('hex');
next();
});
app.use(helmet({
contentSecurityPolicy: {
directives: {
scriptSrc: ['self', (req, res) => `'nonce-${res.locals.nonce}'`],
},
},
}));
// <script nonce="<%= nonce %>">initApp();</script>
XSS 跨站脚本
恶意脚本注入到你的页面中
XSS 是攻击者把恶意 JavaScript 注入到网页中,在其他用户浏览时执行。
// React JSX 自动转义(安全)
const userInput = '<script>alert("xss")</script>';
return <div>{userInput}</div>; // 自动转义
// 需要渲染 HTML 时使用 DOMPurify
import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(dirtyHtml, {
ALLOWED_TAGS: ['b', 'i', 'a', 'p'],
});
// 配置 CSP 头(Content Security Policy)
import helmet from 'helmet';
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
},
},
}));
CSRF 跨站伪造
诱导用户在不知情下执行操作
// 防御1:SameSite Cookie(最简单有效)
app.use(session({
cookie: {
httpOnly: true, // JS 无法读取
secure: true, // 仅 HTTPS
sameSite: 'strict', // 不允许跨站发送
},
}));
// 防御2:验证 Origin 头
function csrfCheck(req, res, next) {
const origin = req.headers.origin;
const allowed = ['https://myapp.com'];
if (req.method !== 'GET' && !allowed.some(o => origin?.startsWith(o))) {
return res.status(403).json({ error: 'CSRF check failed' });
}
next();
}
SQL 注入
Prisma 默认参数化查询,天然免疫
// Prisma 自动参数化(安全)
const user = await prisma.user.findUnique({
where: { email: userInput }, // 安全
});
// 危险:SQL 字符串拼接(绝对不要这样做!)
// const q = `SELECT * FROM users WHERE email = '${userInput}'`
// userInput = "admin' OR '1'='1" → 返回所有用户!
// 安全:Prisma tagged template 自动参数化
const result = await prisma.$queryRaw`
SELECT * FROM "User" WHERE email = ${userInput}
`;
依赖审计
你的第三方库可能有漏洞
# npm audit 检查已知漏洞
$ npm audit
$ npm audit --production # 只检查生产依赖
$ npm audit fix # 自动修复
# 在 CI 中集成安全审计
# .github/workflows/security.yml
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm audit --audit-level=high
Part 4: 密钥管理
密钥泄露是最常见的安全事故原因。
环境变量管理
用 Zod 验证,启动时报错好过运行时崩溃
// src/env.ts
import { z } from 'zod';
const envSchema = z.object({
NODE_ENV: z.enum(['development', 'production', 'test']),
DATABASE_URL: z.string().url(),
JWT_SECRET: z.string().min(32),
PORT: z.coerce.number().default(3000),
});
export const env = envSchema.parse(process.env);
// 缺少必要变量时直接报错退出
# .gitignore 中必须排除
.env
.env.local
.env.production
密码与 JWT
bcrypt 哈希 + 短期 Token
// 密码哈希
const hashed = await bcrypt.hash(password, 12);
const isValid = await bcrypt.compare(input, hashed);
// 短期 Access Token
const accessToken = sign(
{ userId: user.id },
env.JWT_SECRET,
{ expiresIn: '15m' }
);
// Refresh Token 存 HttpOnly Cookie
res.cookie('refreshToken', refreshToken, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 7 * 24 * 60 * 60 * 1000,
});
Part 4: 依赖与环境安全
依赖审计、密钥管理——让供应链和环境配置不再成为短板。
依赖审计工具链
从手动检查到自动化门禁
npm audit 对比依赖版本与已知漏洞数据库(Advisory Database)。但仅靠 npm audit 不够——还需要持续监控和自动修复机制。
| 工具 | 定位 | 特点 |
|---|---|---|
| npm audit | 命令行检查 | 内置,零配置 |
| Dependabot | GitHub 原生 | 自动提 PR 升级 |
| Renovate | 跨平台 | 高度可配置,支持分组 |
| Snyk | 深度安全平台 | 容器、IaC 扫描 |
# .github/workflows/security.yml
name: Security Audit
on:
push:
branches: [main]
schedule:
- cron: '0 6 * * 1' # 每周一早上 6 点
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm audit --audit-level=high
continue-on-error: false
Dependabot 配置(.github/dependabot.yml)可以指定哪些依赖需要自动更新、review 人等。对于关键安全依赖,建议设置自动合并低风险升级。
密钥管理最佳实践
环境隔离 + 最小权限 + 定期轮换
密钥管理不是"存到 .env 就完了"。生产环境需要集中式管理,审批流程,以及泄漏后的快速响应能力。
// 1. 环境分离 —— 开发 / 测试 / 生产各自独立密钥
// .env.development
DATABASE_URL=postgresql://localhost/devdb
JWT_SECRET=dev-only-not-secret
// .env.production(由部署平台注入,不入库)
DATABASE_URL=postgresql://prod-db.internal:5432/app
JWT_SECRET=(32+ 字节强随机值)
| 存储方案 | 适用场景 | 安全等级 |
|---|---|---|
| .env + .gitignore | 本地开发 | 基础 |
| GitHub Secrets | CI/CD 流水线 | 良好 |
| 云 KMS (AWS/GCP) | 中大型应用 | 优秀 |
| HashiCorp Vault | 企业级 | 最高 |
• 在任何 Git 仓库中提交 .env 文件
• 在代码中硬编码 API Key 或密码
• 前端代码中存放后端密钥
• 多人共享同一个生产密钥
• 密钥从不轮换(超过 90 天)
密钥轮换流程(双密钥过渡法)
# 1. 生成新密钥
NEW_SECRET=$(openssl rand -hex 32)
# 2. 服务端双密钥并行验证(过渡期 7 天)
# 3. 7 天后下线旧密钥
# 4. 客户端迁移到新密钥
echo "密钥轮换完成"
| 阶段 | 操作 | 验证 |
|---|---|---|
| 准备 | 生成新密钥,通知团队 | 本地测试 |
| 过渡期 | 双密钥并行验证 | 监控错误日志 |
| 收尾 | 下线旧密钥 | 全量回归测试 |
排错:密钥泄露
原因:忘记把 .env 加入 .gitignore,或代码中硬编码了密钥。
修复:(1)立即轮换泄露的密钥;(2)用 BFG Repo-Cleaner 从 Git 历史中删除;(3)用
git-secrets 或 gitleaks 防止再次泄露。
# 用 gitleaks 扫描代码中的密钥
$ gitleaks detect --source . --verbose
安全检查清单
上线前逐一检查
| 类别 | 措施 | 工具 |
|---|---|---|
| XSS | 输入转义、CSP 头 | DOMPurify、Helmet |
| CSRF | SameSite Cookie + Origin 校验 | 自实现 / csrf-csrf |
| SQL 注入 | 参数化查询 | Prisma |
| 认证 | 密码哈希、短 Token | bcrypt、JWT |
| HTTPS | 全站 HTTPS | Let's Encrypt |
| 依赖 | 定期审计 | npm audit |
| 速率限制 | 防暴力破解 | express-rate-limit |
| 输入验证 | 验证所有输入 | Zod |
试一试
练习一
任务:为你的 Express 应用添加速率限制,每个 IP 每分钟最多 100 次请求。
参考答案
import rateLimit from 'express-rate-limit';
const limiter = rateLimit({
windowMs: 60 * 1000,
max: 100,
message: '请求太频繁,请稍后再试',
});
app.use('/api/', limiter);
练习二
任务:运行 npm audit 检查你的项目依赖,修复所有高危漏洞。
$ npm audit
$ npm audit fix
练习三:CORS 修复
解决本地前后端分离的跨域障碍
场景:前端运行在 http://localhost:5173,后端 API 在 http://localhost:3000。前端调用 fetch('http://localhost:3000/api/data') 被浏览器拦截,控制台报 Access-Control-Allow-Origin 错误。
任务:在后端 Express 中添加 CORS 中间件,使前端可以跨域请求,同时允许携带 Cookie。
参考答案
import cors from 'cors';
app.use(cors({
origin: 'http://localhost:5173', // 明确指定前端来源
credentials: true, // 允许 Cookie
methods: ['GET', 'POST', 'PUT', 'DELETE'],
}));
练习四:Helmet 安全头配置
CSP / HSTS / X-Content-Type-Options
任务:使用 Helmet 中间件一次性启用多个安全头。要求:
① CSP 仅允许同源脚本和样式
② HSTS 强制 HTTPS(max-age 至少 30 天)
③ 禁用 MIME 类型嗅探(X-Content-Type-Options: nosniff)
参考答案
import helmet from 'helmet';
app.use(helmet()); // 自动启用 X-Content-Type-Options: nosniff 等
// 手动追加 HSTS(Helmet 默认不开启严格 HSTS)
app.use(helmet.hsts({
maxAge: 2592000, // 30 天(单位:秒)
includeSubDomains: true,
preload: true,
}));
// CSP 配置(需手动启用)
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:"],
},
}));
📝 要点总结
本章核心知识点
缓存策略
Redis 缓存热点数据,连接池复用数据库连接,索引加速查询
前端性能
懒加载减少首屏体积,代码分割按需加载,图片格式优化
安全防御
输入验证 + 参数化查询 + CSP = 防御 XSS/SQL 注入/CSRF
密钥管理
环境变量 + 定期轮换 + 依赖审计 = 安全的密钥生命周期
恭喜你完成了全部课程
从 JavaScript 入门到全栈部署,你已经掌握了 Node.js 全栈开发的核心技能。继续实践,持续学习,写出更好的代码。