Nginx 完全教程

Nginx 完全教程

从零开始掌握 Nginx 的安装、配置与运维。


01. Nginx 简介

Nginx (发音 "engine-x")是一个高性能的 HTTP 和反向代理服务器,由 Igor Sysoev 于 2004 年首次发布。它以事件驱动、异步非阻塞的架构闻名,能够以极低的内存消耗处理数万并发连接。

Nginx 能做什么?

  • HTTP 服务器 --- 高效提供静态文件服务
  • 反向代理 --- 将请求转发给后端应用服务器
  • 负载均衡 --- 在多台后端之间分发流量
  • SSL/TLS 终端 --- 处理 HTTPS 加密解密
  • API 网关 --- 路由、限流、鉴权
  • 邮件代理 --- IMAP/POP3/SMTP 代理

Nginx vs Apache

特性 Nginx Apache
架构模型 事件驱动 / 异步非阻塞 进程 / 线程驱动
并发性能 极高(C10K+) 中等
内存占用 相对较高
静态文件 极快 良好
动态内容 需借助外部(PHP-FPM等) 内置 mod_php
配置风格 集中式、声明式 .htaccess 分散式

02. Linux 安装与启动

Ubuntu / Debian

bash 复制代码
# 更新软件源并安装
sudo apt update
sudo apt install -y nginx
​
# 启动 & 设为开机自启
sudo systemctl start nginx
sudo systemctl enable nginx
​
# 验证安装
nginx -v

CentOS / RHEL / Fedora

bash 复制代码
# CentOS 7
sudo yum install -y epel-release
sudo yum install -y nginx
​
# CentOS 8+ / Fedora
sudo dnf install -y nginx
​
sudo systemctl start nginx
sudo systemctl enable nginx

macOS (Homebrew)

sql 复制代码
brew install nginx
brew services start nginx

Docker

bash 复制代码
docker run -d --name nginx \
  -p 80:80 \
  -v /path/to/html:/usr/share/nginx/html \
  -v /path/to/nginx.conf:/etc/nginx/nginx.conf \
  nginx:stable

常用管理命令

r 复制代码
nginx -t              # 测试配置语法
nginx -T              # 测试并输出完整配置
nginx -s reload       # 平滑重载(不中断服务)
nginx -s stop         # 快速停止
nginx -s quit         # 优雅停止
nginx -s reopen       # 重新打开日志
nginx -V              # 查看编译参数

提示 生产环境推荐使用 nginx -s reload 来应用配置变更,该操作是零停机的。


03. Windows 安装与配置

Nginx 官方提供 Windows 预编译包。虽然 Windows 版主要用于开发和测试,但掌握其安装方法对本地开发非常有用。

注意 Windows 版 Nginx 不支持 select 以外的事件模型,性能远不如 Linux 版。 生产环境请使用 Linux。

方法一:下载官方压缩包

  1. 下载 Nginx访问 nginx.org/download,下载 Stable version 的 Windows zip 包。
  2. 解压到目标目录建议解压到 C:\nginx,路径中不要包含中文或空格。
  3. 启动 Nginx
bash 复制代码
cd C:\nginx
.\nginx.exe
​
# 验证:浏览器访问 http://localhost
tasklist /fi "imagename eq nginx.exe"

Windows 管理命令

bash 复制代码
cd C:\nginx
.\nginx.exe -t             # 测试配置
.\nginx.exe -s reload      # 重载配置
.\nginx.exe -s stop        # 快速停止
.\nginx.exe -s quit        # 优雅停止

重要 Windows 没有 systemctl ,Nginx 不会自动作为服务运行。关闭命令行后 Nginx 仍在后台,需用 nginx -s stop 或任务管理器终止。

端口冲突排查

bash 复制代码
netstat -ano | findstr ":80 "
iisreset /stop             # 停止 IIS
net stop W3SVC             # 停止 WWW 服务

方法二:Chocolatey

复制代码
choco install nginx -y

方法三:Scoop

csharp 复制代码
scoop bucket add main
scoop install nginx

方法四:Docker Desktop

bash 复制代码
version: "3.8"
services:
  nginx:
    image: nginx:stable
    ports: ["80:80", "443:443"]
    volumes:
      - ./html:/usr/share/nginx/html
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    restart: unless-stopped

注册为系统服务(NSSM)

swift 复制代码
# 下载: https://nssm.cc/download
nssm install Nginx "C:\nginx\nginx.exe"
nssm set Nginx AppDirectory "C:\nginx"
nssm set Nginx Start SERVICE_AUTO_START
nssm start Nginx

Windows 配置示例

ini 复制代码
worker_processes 1;

events { worker_connections 1024; }

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile  on;
    gzip     on;

    server {
        listen 80;
        server_name localhost;

        location / {
            root html;
            index index.html;
        }

        location /api/ {
            proxy_pass http://127.0.0.1:3000;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }
    }
    include conf.d/*.conf;
}

各平台安装方式对比

平台 推荐方式 命令 场景
Ubuntu/Debian apt apt install nginx 生产
CentOS/RHEL dnf dnf install nginx 生产
macOS Homebrew brew install nginx 开发
Windows zip 下载解压 开发
Windows Chocolatey choco install nginx 开发
Windows Scoop scoop install nginx 开发
Windows Docker docker run nginx 一致性

04. 核心概念

请求处理模型

Nginx 采用一个 Master 进程 + 多个 Worker 进程的模型。

scss 复制代码
Client Request
   │
   ▼
┌──────────────────────────────┐
│       Master Process         │
│   (读取配置, 管理 Worker)     │
└──────┬───────────┬───────────┘
       │           │
  ┌────▼────┐ ┌────▼────┐
  │Worker 1 │ │Worker 2 │
  │ (epoll) │ │ (epoll) │
  └────┬────┘ └────┬────┘
       │           │
  ┌────▼────┐ ┌────▼────┐
  │Backend A│ │Backend B│
  └─────────┘ └─────────┘

关键目录

路径 Linux Windows
主配置 /etc/nginx/nginx.conf C:\nginx\conf\nginx.conf
附加配置 /etc/nginx/conf.d/ C:\nginx\conf.d\
站点根 /var/www/ C:\nginx\html\
日志 /var/log/nginx/ C:\nginx\logs\
MIME /etc/nginx/mime.types C:\nginx\conf\mime.types

05. 配置文件结构

ini 复制代码
user              nginx;
worker_processes  auto;
error_log         /var/log/nginx/error.log warn;
pid               /var/run/nginx.pid;

events {
    worker_connections 1024;
    use epoll;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    sendfile    on;
    gzip        on;
    include     /etc/nginx/conf.d/*.conf;

    server {
        listen      80;
        server_name example.com;
        root        /var/www/example;
        location /      { index index.html; }
        location /api/  { proxy_pass http://127.0.0.1:3000; }
    }
}

配置层次

css 复制代码
main        ← 全局
├── events    ← 事件
└── http      ← HTTP
      ├── server  ← 虚拟主机
      │   └── location ← URL
      └── upstream ← 后端组

注意 修改配置后务必先执行 nginx -t 测试语法。


06. 虚拟主机

ini 复制代码
server {
    listen 80;
    server_name site-a.com www.site-a.com;
    root /var/www/site-a;
    location / { try_files $uri $uri/ =404; }
}

# 默认服务器 --- 处理未匹配请求
server {
    listen 80 default_server;
    server_name _;
    return 444;
}

07. Location 路由匹配

语法 类型 优先级
= /path 精确匹配 最高
^~ /path 前缀匹配
~ pattern 正则(区分大小写)
~* pattern 正则(不区分)
/path 普通前缀

实战示例

bash 复制代码
location = /                    { proxy_pass http://127.0.0.1:3000/home; }
location ~* .(js|css|png|jpg)$  { expires 30d; access_log off; }
location ^~ /api/                { proxy_pass http://backend; }
location /                       { try_files $uri $uri/ /index.html; }
location ~ /.                    { deny all; }

try_files try_files <math xmlns="http://www.w3.org/1998/Math/MathML"> u r i uri </math>uriuri/ /index.html --- 先尝试文件 → 再尝试目录 → 都不存在返回 index.html(适合 SPA)。


08. 反向代理

ini 复制代码
server {
    listen 80;
    server_name api.example.com;
    location / {
        proxy_pass         http://127.0.0.1:3000;
        proxy_set_header   Host $host;
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
        proxy_connect_timeout 60s;
        proxy_read_timeout    60s;
    }
}

09. 静态资源服务

bash 复制代码
server {
    listen 80;
    root /var/www/static;
    sendfile on;
    gzip on;
    gzip_types text/plain text/css application/javascript
               application/json image/svg+xml font/woff2;

    location ~* .(js|css|png|jpg|svg|woff2)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
    }
    location ~ /. { deny all; }
}

10. HTTPS / SSL

Let's Encrypt

bash 复制代码
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com
echo "0 3 * * * certbot renew --quiet" | sudo crontab -

手动配置

ini 复制代码
server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name example.com;
    ssl_certificate      /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key  /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_protocols        TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
    root /var/www/example;
}

11. 负载均衡

算法 指令 说明
轮询 --- 按顺序分配
加权轮询 weight=N 按权重分配
IP Hash ip_hash 同 IP 同后端
最少连接 least_conn 最少连接优先
ini 复制代码
upstream backend {
    server 10.0.0.1:8080 weight=3;
    server 10.0.0.2:8080 weight=2;
    server 10.0.0.3:8080 backup;
}

server {
    listen 80;
    location / {
        proxy_pass http://backend;
        proxy_next_upstream error timeout http_502 http_503;
    }
}

12. 缓存配置

ini 复制代码
proxy_cache_path /var/cache/nginx
    levels=1:2 keys_zone=my_cache:10m
    max_size=1g inactive=60m use_temp_path=off;

location / {
    proxy_pass http://backend;
    proxy_cache my_cache;
    proxy_cache_valid 200 302 10m;
    proxy_cache_valid 404 1m;
    add_header X-Cache-Status $upstream_cache_status;
}

13. Rewrite 规则

bash 复制代码
# 强制 www
server {
    listen 80;
    server_name example.com;
    return 301 http://www.example.com$request_uri;
}

# URL 美化
rewrite ^/article/(\d+)$ /article.php?id=$1 last;

# 旧路径重定向
rewrite ^/old-page$ /new-page permanent;

14. WebSocket 代理

ini 复制代码
location /ws/ {
    proxy_pass http://ws_backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 3600s;
    proxy_buffering off;
}

15. 安全加固

安全响应头

scss 复制代码
server_tokens off;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

限流

ini 复制代码
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;

location /api/ {
    limit_req zone=api burst=20 nodelay;
    limit_req_status 429;
    proxy_pass http://backend;
}

16. 性能优化

ini 复制代码
worker_processes auto;
worker_rlimit_nofile 65535;

events {
    worker_connections 10240;
    multi_accept on;
    use epoll;
}

http {
    sendfile    on;
    tcp_nopush  on;
    tcp_nodelay on;
    keepalive_timeout  65;
    keepalive_requests 1000;
    gzip on;
    gzip_comp_level 4;
    gzip_static on;
}

17. 日志与监控

JSON 日志

css 复制代码
log_format json_log escape=json
    '{"time":"$time_iso8601","addr":"$remote_addr",'
    '"method":"$request_method","uri":"$request_uri",'
    '"status":$status,"time_s":$request_time}';

access_log /var/log/nginx/access.json json_log;

常用变量

变量 说明
$remote_addr 客户端 IP
$request_uri 完整 URI
$status 状态码
$request_time 处理时间
$upstream_cache_status 缓存状态

18. 常见问题

Q: 配置修改后不生效?

nginx -t 测试语法,再 nginx -s reload 重载。

Q: 502 Bad Gateway?

  • 后端服务是否运行
  • proxy_pass 地址端口是否正确
  • 防火墙是否放行
  • 查看 error.log

Q: 504 Gateway Timeout?

ini 复制代码
proxy_connect_timeout 120s;
proxy_read_timeout    120s;

Q: 413 Request Entity Too Large?

ini 复制代码
client_max_body_size 100m;

Q: Windows 启动失败?

  • 检查 80 端口占用:netstat -ano | findstr ":80"
  • 运行 nginx -t 检查配置
  • 路径中不要有中文或空格
  • 以管理员权限运行

Q: CORS 跨域配置?

bash 复制代码
location /api/ {
    add_header Access-Control-Allow-Origin $http_origin always;
    add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
    add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
    if ($request_method = OPTIONS) { return 204; }
    proxy_pass http://backend;
}

学习资源

  • Nginx 官方文档
  • Nginx GitHub
  • Nginx Admin's Handbook
  • NSSM

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nginx 完全教程</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;600;700&family=Noto+Serif+SC:wght@400;600;700&family=DM+Sans:wght@400;500;700&display=swap" rel="stylesheet">
<style>
  :root, [data-theme="dark"] {
    --bg: #0a0e14; --bg-surface: #111820; --bg-card: #151d28;
    --bg-code: #0d1117; --accent: #00e59b; --accent-dim: #00e59b33;
    --accent-glow: #00e59b15; --text-primary: #e6edf3;
    --text-body: #b0bec5; --text-muted: #5c6b7a;
    --border: #1e2a38; --border-accent: #00e59b44;
    --tag-blue: #58a6ff; --tag-orange: #f0883e; --tag-purple: #bc8cff;
    --tag-red: #ff7b72; --tag-yellow: #e3b341;
    --info-bg: #00e59b0d; --info-text: #b0f0d8;
    --warn-bg: #e3b3410d; --warn-text: #f0dca0;
    --danger-bg: #ff7b720d; --danger-text: #ffc1bb;
    --hero-gradient: radial-gradient(ellipse at 30% 0%, #00e59b22 0%, transparent 60%);
    --nav-hover-bg: #00e59b10; --table-hover: #00e59b08;
    --scrollbar-thumb: #1e2a38; --noise-opacity: 0.03;
    --toggle-bg: #1e2a38; --toggle-icon: #e3b341;
    --shadow-sm: 0 1px 3px rgba(0,0,0,0.3);
    --shadow-md: 0 4px 12px rgba(0,0,0,0.4);
  }
  [data-theme="light"] {
    --bg: #f8f6f1; --bg-surface: #ffffff; --bg-card: #ffffff;
    --bg-code: #f4f1ec; --accent: #00875a; --accent-dim: #00875a22;
    --accent-glow: #00875a0a; --text-primary: #1a1a1a;
    --text-body: #4a4a4a; --text-muted: #8a8a8a;
    --border: #e0ddd6; --border-accent: #00875a44;
    --tag-blue: #0969da; --tag-orange: #bc4c00; --tag-purple: #8250df;
    --tag-red: #cf222e; --tag-yellow: #9a6700;
    --info-bg: #00875a10; --info-text: #005a3a;
    --warn-bg: #9a670010; --warn-text: #6b4c00;
    --danger-bg: #cf222e10; --danger-text: #8c1d18;
    --hero-gradient: radial-gradient(ellipse at 30% 0%, #00875a15 0%, transparent 60%);
    --nav-hover-bg: #00875a08; --table-hover: #00875a06;
    --scrollbar-thumb: #d0cdc6; --noise-opacity: 0.015;
    --toggle-bg: #e8e5de; --toggle-icon: #00875a;
    --shadow-sm: 0 1px 3px rgba(0,0,0,0.08);
    --shadow-md: 0 4px 12px rgba(0,0,0,0.1);
  }
  * { margin: 0; padding: 0; box-sizing: border-box; }
  html { scroll-behavior: smooth; }
  body {
    font-family: 'DM Sans', sans-serif; background: var(--bg);
    color: var(--text-body); line-height: 1.75; font-size: 16px;
    overflow-x: hidden; transition: background 0.35s, color 0.35s;
  }
  body::before {
    content: ''; position: fixed; inset: 0;
    background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='1'/%3E%3C/svg%3E");
    opacity: var(--noise-opacity); pointer-events: none; z-index: 9999;
  }
  .layout { display: grid; grid-template-columns: 280px 1fr; min-height: 100vh; }
  .sidebar {
    position: sticky; top: 0; height: 100vh; background: var(--bg-surface);
    border-right: 1px solid var(--border); padding: 32px 0;
    overflow-y: auto; scrollbar-width: thin;
    scrollbar-color: var(--scrollbar-thumb) transparent;
    display: flex; flex-direction: column;
    transition: background 0.35s, border-color 0.35s;
  }
  .sidebar-logo {
    padding: 0 24px 28px; border-bottom: 1px solid var(--border);
    margin-bottom: 20px; display: flex; align-items: center;
    justify-content: space-between;
  }
  .sidebar-logo-left h2 {
    font-family: 'JetBrains Mono', monospace; font-size: 20px;
    font-weight: 700; color: var(--text-primary);
    display: flex; align-items: center; gap: 10px;
  }
  .sidebar-logo-left h2 .icon {
    width: 32px; height: 32px; background: var(--accent);
    border-radius: 6px; display: flex; align-items: center;
    justify-content: center; color: #fff; font-weight: 700; font-size: 16px;
  }
  .sidebar-logo-left p {
    font-size: 12px; color: var(--text-muted); margin-top: 6px;
    font-family: 'JetBrains Mono', monospace;
  }
  .theme-toggle {
    width: 44px; height: 24px; border-radius: 12px;
    background: var(--toggle-bg); border: 1px solid var(--border);
    cursor: pointer; position: relative; flex-shrink: 0;
    transition: background 0.3s;
  }
  .theme-toggle::after {
    content: ''; position: absolute; top: 2px; left: 2px;
    width: 18px; height: 18px; border-radius: 50%;
    background: var(--toggle-icon);
    transition: transform 0.3s ease; box-shadow: 0 1px 3px rgba(0,0,0,0.2);
  }
  [data-theme="light"] .theme-toggle::after { transform: translateX(20px); }
  .theme-toggle .toggle-label {
    position: absolute; top: 50%; transform: translateY(-50%);
    font-size: 10px; pointer-events: none;
  }
  .theme-toggle .sun { right: 5px; opacity: 0.4; }
  .theme-toggle .moon { left: 5px; opacity: 1; }
  [data-theme="light"] .theme-toggle .sun { opacity: 1; }
  [data-theme="light"] .theme-toggle .moon { opacity: 0.4; }
  .sidebar-nav-scroll { flex: 1; overflow-y: auto; }
  .nav-group { margin-bottom: 8px; }
  .nav-group-title {
    font-family: 'JetBrains Mono', monospace; font-size: 10px;
    font-weight: 600; text-transform: uppercase; letter-spacing: 0.12em;
    color: var(--text-muted); padding: 12px 24px 6px;
  }
  .nav-link {
    display: block; padding: 8px 24px; font-size: 13.5px;
    color: var(--text-body); text-decoration: none;
    border-left: 2px solid transparent;
    font-family: 'JetBrains Mono', monospace; font-weight: 400;
    transition: color 0.2s, background 0.2s, border-color 0.2s;
  }
  .nav-link:hover {
    color: var(--accent); background: var(--nav-hover-bg);
    border-left-color: var(--accent);
  }
  .nav-link.active {
    color: var(--accent); border-left-color: var(--accent);
    background: var(--nav-hover-bg);
  }
  .sidebar-footer {
    padding: 16px 24px; border-top: 1px solid var(--border); flex-shrink: 0;
  }
  .sidebar-export-btn {
    display: flex; align-items: center; justify-content: center; gap: 8px;
    width: 100%; padding: 9px 16px; background: var(--accent-dim);
    border: 1px solid var(--border-accent); border-radius: 6px;
    color: var(--accent); font-family: 'JetBrains Mono', monospace;
    font-size: 12px; font-weight: 600; cursor: pointer;
    transition: background 0.2s, color 0.2s;
  }
  .sidebar-export-btn:hover { background: var(--accent); color: #fff; }
  .sidebar-export-btn svg { flex-shrink: 0; }
  .main { max-width: 860px; padding: 56px 64px 120px; }
  .hero { margin-bottom: 64px; position: relative; }
  .hero::before {
    content: ''; position: absolute; top: -56px; left: -64px; right: -64px;
    height: 400px; background: var(--hero-gradient); pointer-events: none;
  }
  .hero-tag {
    font-family: 'JetBrains Mono', monospace; font-size: 12px;
    font-weight: 600; color: var(--accent); text-transform: uppercase;
    letter-spacing: 0.15em; margin-bottom: 16px; position: relative;
  }
  .hero h1 {
    font-family: 'Noto Serif SC', serif; font-size: 48px;
    font-weight: 700; color: var(--text-primary); line-height: 1.2;
    margin-bottom: 20px; position: relative;
  }
  .hero p { font-size: 17px; color: var(--text-body); max-width: 600px; position: relative; }
  .hero-meta {
    margin-top: 28px; display: flex; gap: 24px;
    font-family: 'JetBrains Mono', monospace;
    font-size: 12px; color: var(--text-muted); position: relative;
  }
  .hero-meta span { display: flex; align-items: center; gap: 6px; }
  .hero-meta .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); }
  .hero-actions {
    margin-top: 32px; position: relative;
    display: flex; gap: 12px; flex-wrap: wrap;
  }
  .export-btn {
    display: inline-flex; align-items: center; gap: 8px;
    padding: 11px 28px; background: var(--accent); color: #fff;
    border: none; border-radius: 8px;
    font-family: 'JetBrains Mono', monospace; font-size: 13px;
    font-weight: 600; cursor: pointer; transition: all 0.2s;
  }
  .export-btn:hover { opacity: 0.88; transform: translateY(-1px); box-shadow: 0 6px 24px var(--accent-dim); }
  .export-btn svg { flex-shrink: 0; }
  .copy-btn {
    display: inline-flex; align-items: center; gap: 8px;
    padding: 11px 24px; background: transparent;
    color: var(--text-body); border: 1px solid var(--border);
    border-radius: 8px; font-family: 'JetBrains Mono', monospace;
    font-size: 13px; font-weight: 500; cursor: pointer;
    transition: border-color 0.2s, color 0.2s;
  }
  .copy-btn:hover { border-color: var(--accent); color: var(--accent); }
  .copy-btn svg { flex-shrink: 0; }
  .section { margin-bottom: 72px; animation: fadeUp 0.5s ease forwards; opacity: 0; }
  .section:nth-child(2) { animation-delay: 0.05s; }
  .section:nth-child(3) { animation-delay: 0.1s; }
  .section:nth-child(4) { animation-delay: 0.15s; }
  @keyframes fadeUp { from { opacity:0; transform:translateY(16px); } to { opacity:1; transform:translateY(0); } }
  .section-anchor { display: block; position: relative; top: -80px; visibility: hidden; }
  .section-header {
    display: flex; align-items: center; gap: 14px;
    margin-bottom: 24px; padding-bottom: 16px;
    border-bottom: 1px solid var(--border);
  }
  .section-number {
    font-family: 'JetBrains Mono', monospace; font-size: 13px;
    font-weight: 700; color: #fff; background: var(--accent);
    width: 30px; height: 30px; border-radius: 6px;
    display: flex; align-items: center; justify-content: center;
  }
  .section-header h2 {
    font-family: 'Noto Serif SC', serif; font-size: 28px;
    font-weight: 700; color: var(--text-primary);
  }
  h3 {
    font-family: 'DM Sans', sans-serif; font-size: 20px;
    font-weight: 700; color: var(--text-primary); margin: 36px 0 14px;
  }
  h4 {
    font-family: 'JetBrains Mono', monospace; font-size: 14px;
    font-weight: 600; color: var(--tag-blue); margin: 28px 0 10px;
    text-transform: uppercase; letter-spacing: 0.06em;
  }
  p { margin-bottom: 16px; }
  strong { color: var(--text-primary); font-weight: 600; }
  a { color: var(--accent); text-decoration: none; }
  a:hover { text-decoration: underline; }
  .code-block {
    position: relative; margin: 20px 0 28px; border-radius: 10px;
    overflow: hidden; border: 1px solid var(--border);
    background: var(--bg-code); box-shadow: var(--shadow-sm);
  }
  .code-header {
    display: flex; align-items: center; justify-content: space-between;
    padding: 10px 16px; background: var(--bg-surface);
    border-bottom: 1px solid var(--border);
  }
  .code-header .lang {
    font-family: 'JetBrains Mono', monospace; font-size: 11px;
    font-weight: 600; color: var(--text-muted);
    text-transform: uppercase; letter-spacing: 0.08em;
  }
  .code-header .dots { display: flex; gap: 6px; }
  .code-header .dots span { width: 10px; height: 10px; border-radius: 50%; }
  .code-header .dots span:nth-child(1) { background: #ff5f57; }
  .code-header .dots span:nth-child(2) { background: #ffbd2e; }
  .code-header .dots span:nth-child(3) { background: #28c840; }
  pre {
    padding: 20px; overflow-x: auto;
    font-family: 'JetBrains Mono', monospace;
    font-size: 13.5px; line-height: 1.7; color: var(--text-primary);
  }
  pre code { font-family: inherit; }
  .cm  { color: var(--text-muted); }
  .kw  { color: var(--tag-purple); }
  .fn  { color: #d2a8ff; }
  .st  { color: var(--tag-blue); }
  .nb  { color: var(--tag-orange); }
  .nr  { color: var(--tag-yellow); }
  .at  { color: var(--accent); }
  [data-theme="light"] .fn { color: #8250df; }
  code:not(pre code) {
    font-family: 'JetBrains Mono', monospace; font-size: 0.88em;
    background: var(--accent-dim); color: var(--accent);
    padding: 2px 7px; border-radius: 4px; font-weight: 500;
  }
  ul, ol { margin: 0 0 20px 24px; }
  li { margin-bottom: 8px; padding-left: 4px; }
  li::marker { color: var(--accent); }
  .info-box {
    padding: 18px 20px; border-radius: 8px; margin: 24px 0;
    font-size: 14.5px; border-left: 3px solid;
  }
  .info-box.tip { background: var(--info-bg); border-color: var(--accent); color: var(--info-text); }
  .info-box.warn { background: var(--warn-bg); border-color: var(--tag-yellow); color: var(--warn-text); }
  .info-box.danger { background: var(--danger-bg); border-color: var(--tag-red); color: var(--danger-text); }
  .info-box .label {
    font-family: 'JetBrains Mono', monospace; font-size: 11px;
    font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em;
    margin-bottom: 6px; display: block;
  }
  .info-box.tip .label { color: var(--accent); }
  .info-box.warn .label { color: var(--tag-yellow); }
  .info-box.danger .label { color: var(--tag-red); }
  .table-wrap {
    overflow-x: auto; margin: 20px 0 28px; border-radius: 8px;
    border: 1px solid var(--border); box-shadow: var(--shadow-sm);
  }
  table { width: 100%; border-collapse: collapse; font-size: 14px; }
  thead { background: var(--bg-surface); }
  th {
    font-family: 'JetBrains Mono', monospace; font-size: 11px;
    font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em;
    color: var(--text-muted); text-align: left; padding: 12px 16px;
    border-bottom: 1px solid var(--border);
  }
  td {
    padding: 12px 16px; border-bottom: 1px solid var(--border);
    color: var(--text-body);
  }
  tr:last-child td { border-bottom: none; }
  tr:hover td { background: var(--table-hover); }
  td code { font-size: 12.5px; }
  .diagram {
    background: var(--bg-code); border: 1px solid var(--border);
    border-radius: 10px; padding: 28px 24px; margin: 24px 0;
    font-family: 'JetBrains Mono', monospace; font-size: 13px;
    line-height: 1.6; color: var(--text-body); overflow-x: auto; white-space: pre;
  }
  .diagram .highlight { color: var(--accent); font-weight: 600; }
  .diagram .accent2 { color: var(--tag-blue); }
  .steps { counter-reset: step; list-style: none; margin: 20px 0; padding: 0; }
  .steps li { counter-increment: step; position: relative; padding-left: 48px; margin-bottom: 20px; }
  .steps li::before {
    content: counter(step); position: absolute; left: 0; top: 0;
    width: 32px; height: 32px; border-radius: 50%;
    background: var(--accent-dim); border: 1px solid var(--accent);
    color: var(--accent); font-family: 'JetBrains Mono', monospace;
    font-size: 13px; font-weight: 700;
    display: flex; align-items: center; justify-content: center;
  }
  .divider { height: 1px; background: var(--border); margin: 48px 0; }
  .mobile-toggle {
    display: none; position: fixed; top: 16px; left: 16px; z-index: 1000;
    width: 40px; height: 40px; border-radius: 8px;
    background: var(--bg-surface); border: 1px solid var(--border);
    color: var(--text-primary); font-size: 20px; cursor: pointer;
    align-items: center; justify-content: center;
  }
  .mobile-theme-btn {
    display: none; position: fixed; top: 16px; right: 16px; z-index: 1000;
    width: 40px; height: 40px; border-radius: 8px;
    background: var(--bg-surface); border: 1px solid var(--border);
    color: var(--text-primary); font-size: 18px; cursor: pointer;
    align-items: center; justify-content: center;
  }
  .sidebar-overlay {
    display: none; position: fixed; inset: 0; z-index: 998;
    background: rgba(0,0,0,0.4);
  }
  .sidebar-overlay.show { display: block; }
  .toast {
    position: fixed; bottom: 32px; right: 32px; z-index: 10000;
    padding: 14px 28px; border-radius: 10px;
    font-family: 'JetBrains Mono', monospace; font-size: 13px;
    font-weight: 600; box-shadow: var(--shadow-md);
    display: flex; align-items: center; gap: 10px;
    animation: toastIn 0.3s ease;
  }
  .toast.success { background: var(--accent); color: #fff; }
  .toast.info { background: var(--tag-blue); color: #fff; }
  @keyframes toastIn { from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
  @media (max-width: 900px) {
    .layout { grid-template-columns: 1fr; }
    .sidebar { position: fixed; left: -300px; top: 0; width: 280px; z-index: 999; transition: left 0.3s; }
    .sidebar.open { left: 0; }
    .mobile-toggle, .mobile-theme-btn { display: flex; }
    .main { padding: 80px 24px 80px; }
    .hero h1 { font-size: 32px; }
    .hero::before { left: -24px; right: -24px; }
  }
  ::-webkit-scrollbar { width: 6px; height: 6px; }
  ::-webkit-scrollbar-track { background: transparent; }
  ::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb); border-radius: 3px; }
</style>
</head>
<body>

<button class="mobile-toggle" id="mobileToggle">☰</button>
<button class="mobile-theme-btn" id="mobileThemeBtn">◑</button>
<div class="sidebar-overlay" id="overlay"></div>

<div class="layout">

<nav class="sidebar" id="sidebar">
  <div class="sidebar-logo">
    <div class="sidebar-logo-left">
      <h2><span class="icon">N</span> Nginx 教程</h2>
      <p>v1.24 · 完全指南</p>
    </div>
    <div class="theme-toggle" id="themeToggle" title="切换主题">
      <span class="toggle-label sun">☀</span>
      <span class="toggle-label moon">☾</span>
    </div>
  </div>
  <div class="sidebar-nav-scroll" id="navScroll">
    <div class="nav-group">
      <div class="nav-group-title">入门基础</div>
      <a href="#intro" class="nav-link active">简介</a>
      <a href="#install-linux" class="nav-link">Linux 安装</a>
      <a href="#install-windows" class="nav-link">Windows 安装</a>
      <a href="#concepts" class="nav-link">核心概念</a>
    </div>
    <div class="nav-group">
      <div class="nav-group-title">配置详解</div>
      <a href="#structure" class="nav-link">配置文件结构</a>
      <a href="#virtual-host" class="nav-link">虚拟主机</a>
      <a href="#location" class="nav-link">Location 路由</a>
      <a href="#proxy" class="nav-link">反向代理</a>
    </div>
    <div class="nav-group">
      <div class="nav-group-title">进阶功能</div>
      <a href="#static" class="nav-link">静态资源服务</a>
      <a href="#ssl" class="nav-link">HTTPS / SSL</a>
      <a href="#loadbalance" class="nav-link">负载均衡</a>
      <a href="#cache" class="nav-link">缓存配置</a>
      <a href="#rewrite" class="nav-link">Rewrite 规则</a>
    </div>
    <div class="nav-group">
      <div class="nav-group-title">实战与运维</div>
      <a href="#websocket" class="nav-link">WebSocket 代理</a>
      <a href="#security" class="nav-link">安全加固</a>
      <a href="#performance" class="nav-link">性能优化</a>
      <a href="#logging" class="nav-link">日志与监控</a>
      <a href="#faq" class="nav-link">常见问题</a>
    </div>
  </div>
  <div class="sidebar-footer">
    <button class="sidebar-export-btn" id="sidebarExportBtn" title="导出为 Markdown 文件">
      <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
      导出 Markdown
    </button>
  </div>
</nav>

<main class="main">

  <div class="hero">
    <div class="hero-tag">Open-Source Web Server</div>
    <h1>Nginx 完全教程</h1>
    <p>从零开始掌握 Nginx 的安装、配置与运维。涵盖反向代理、负载均衡、HTTPS、缓存、安全加固等核心知识,配合大量实战配置示例。</p>
    <div class="hero-meta">
      <span><span class="dot"></span> 阅读时间约 30 分钟</span>
      <span><span class="dot"></span> 最后更新 2024</span>
      <span><span class="dot"></span> Nginx 1.24+</span>
    </div>
    <div class="hero-actions">
      <button class="export-btn" id="heroExportBtn">
        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
        导出 Markdown 文档
      </button>
      <button class="copy-btn" id="copyBtn">
        <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
        复制到剪贴板
      </button>
    </div>
  </div>

  <!-- 01 -->
  <div class="section" id="intro">
    <span class="section-anchor"></span>
    <div class="section-header"><span class="section-number">01</span><h2>Nginx 简介</h2></div>
    <p><strong>Nginx</strong>(发音 "engine-x")是一个高性能的 HTTP 和反向代理服务器,由 Igor Sysoev 于 2004 年首次发布。它以<strong>事件驱动、异步非阻塞</strong>的架构闻名,能够以极低的内存消耗处理数万并发连接。</p>
    <h3>Nginx 能做什么?</h3>
    <ul>
      <li><strong>HTTP 服务器</strong> --- 高效提供静态文件服务</li>
      <li><strong>反向代理</strong> --- 将请求转发给后端应用服务器</li>
      <li><strong>负载均衡</strong> --- 在多台后端之间分发流量</li>
      <li><strong>SSL/TLS 终端</strong> --- 处理 HTTPS 加密解密</li>
      <li><strong>API 网关</strong> --- 路由、限流、鉴权</li>
      <li><strong>邮件代理</strong> --- IMAP/POP3/SMTP 代理</li>
    </ul>
    <h3>Nginx vs Apache</h3>
    <div class="table-wrap"><table>
      <thead><tr><th>特性</th><th>Nginx</th><th>Apache</th></tr></thead>
      <tbody>
        <tr><td>架构模型</td><td>事件驱动 / 异步非阻塞</td><td>进程 / 线程驱动</td></tr>
        <tr><td>并发性能</td><td>极高(C10K+)</td><td>中等</td></tr>
        <tr><td>内存占用</td><td>低</td><td>相对较高</td></tr>
        <tr><td>静态文件</td><td>极快</td><td>良好</td></tr>
        <tr><td>动态内容</td><td>需借助外部(PHP-FPM等)</td><td>内置 mod_php</td></tr>
        <tr><td>配置风格</td><td>集中式、声明式</td><td>.htaccess 分散式</td></tr>
      </tbody>
    </table></div>
  </div>

  <!-- 02 -->
  <div class="section" id="install-linux">
    <span class="section-anchor"></span>
    <div class="section-header"><span class="section-number">02</span><h2>Linux 安装与启动</h2></div>
    <h3>Ubuntu / Debian</h3>
    <div class="code-block"><div class="code-header"><div class="dots"><span></span><span></span><span></span></div><span class="lang">bash</span></div>
    <pre><code><span class="cm"># 更新软件源并安装</span>
<span class="kw">sudo</span> apt update
<span class="kw">sudo</span> apt <span class="kw">install</span> -y nginx

<span class="cm"># 启动 &amp; 设为开机自启</span>
<span class="kw">sudo</span> systemctl start nginx
<span class="kw">sudo</span> systemctl enable nginx

<span class="cm"># 验证安装</span>
nginx -v</code></pre></div>
    <h3>CentOS / RHEL / Fedora</h3>
    <div class="code-block"><div class="code-header"><div class="dots"><span></span><span></span><span></span></div><span class="lang">bash</span></div>
    <pre><code><span class="cm"># CentOS 7</span>
<span class="kw">sudo</span> yum install -y epel-release
<span class="kw">sudo</span> yum install -y nginx

<span class="cm"># CentOS 8+ / Fedora</span>
<span class="kw">sudo</span> dnf install -y nginx

<span class="kw">sudo</span> systemctl start nginx
<span class="kw">sudo</span> systemctl enable nginx</code></pre></div>
    <h3>macOS (Homebrew)</h3>
    <div class="code-block"><div class="code-header"><div class="dots"><span></span><span></span><span></span></div><span class="lang">bash</span></div>
    <pre><code>brew install nginx
brew services start nginx</code></pre></div>
    <h3>Docker</h3>
    <div class="code-block"><div class="code-header"><div class="dots"><span></span><span></span><span></span></div><span class="lang">bash</span></div>
    <pre><code>docker run -d --name nginx \
  -p <span class="nr">80</span>:<span class="nr">80</span> \
  -v /path/to/html:/usr/share/nginx/html \
  -v /path/to/nginx.conf:/etc/nginx/nginx.conf \
  nginx:stable</code></pre></div>
    <h3>常用管理命令</h3>
    <div class="code-block"><div class="code-header"><div class="dots"><span></span><span></span><span></span></div><span class="lang">bash</span></div>
    <pre><code>nginx -t              <span class="cm"># 测试配置语法</span>
nginx -T              <span class="cm"># 测试并输出完整配置</span>
nginx -s reload       <span class="cm"># 平滑重载(不中断服务)</span>
nginx -s stop         <span class="cm"># 快速停止</span>
nginx -s quit         <span class="cm"># 优雅停止</span>
nginx -s reopen       <span class="cm"># 重新打开日志</span>
nginx -V              <span class="cm"># 查看编译参数</span></code></pre></div>
    <div class="info-box tip"><span class="label">提示</span>
      生产环境推荐使用 <code>nginx -s reload</code> 来应用配置变更,该操作是零停机的。
    </div>
  </div>

  <!-- 03 -->
  <div class="section" id="install-windows">
    <span class="section-anchor"></span>
    <div class="section-header"><span class="section-number">03</span><h2>Windows 安装与配置</h2></div>
    <p>Nginx 官方提供 Windows 预编译包。虽然 Windows 版主要用于<strong>开发和测试</strong>,但掌握其安装方法对本地开发非常有用。</p>
    <div class="info-box warn"><span class="label">注意</span>
      Windows 版 Nginx 不支持 <code>select</code> 以外的事件模型,性能远不如 Linux 版。<strong>生产环境请使用 Linux。</strong>
    </div>
    <h3>方法一:下载官方压缩包</h3>
    <ol class="steps">
      <li><strong>下载 Nginx</strong><p>访问 <a href="https://nginx.org/en/download.html" target="_blank">nginx.org/download</a>,下载 Stable version 的 Windows zip 包。</p></li>
      <li><strong>解压到目标目录</strong><p>建议解压到 <code>C:\nginx</code>,路径中<strong>不要包含中文或空格</strong>。</p></li>
      <li><strong>启动 Nginx</strong></li>
    </ol>
    <div class="code-block"><div class="code-header"><div class="dots"><span></span><span></span><span></span></div><span class="lang">cmd / PowerShell</span></div>
    <pre><code><span class="kw">cd</span> C:\nginx
.\nginx.exe

<span class="cm"># 验证:浏览器访问 http://localhost</span>
tasklist /fi <span class="st">"imagename eq nginx.exe"</span></code></pre></div>
    <h3>Windows 管理命令</h3>
    <div class="code-block"><div class="code-header"><div class="dots"><span></span><span></span><span></span></div><span class="lang">cmd / PowerShell</span></div>
    <pre><code><span class="kw">cd</span> C:\nginx
.\nginx.exe -t             <span class="cm"># 测试配置</span>
.\nginx.exe -s reload      <span class="cm"># 重载配置</span>
.\nginx.exe -s stop        <span class="cm"># 快速停止</span>
.\nginx.exe -s quit        <span class="cm"># 优雅停止</span></code></pre></div>
    <div class="info-box danger"><span class="label">重要</span>
      Windows 没有 <code>systemctl</code>,Nginx 不会自动作为服务运行。关闭命令行后 Nginx 仍在后台,需用 <code>nginx -s stop</code> 或任务管理器终止。
    </div>
    <h3>端口冲突排查</h3>
    <div class="code-block"><div class="code-header"><div class="dots"><span></span><span></span><span></span></div><span class="lang">PowerShell (管理员)</span></div>
    <pre><code>netstat -ano | findstr <span class="st">":80 "</span>
iisreset /stop             <span class="cm"># 停止 IIS</span>
net stop W3SVC             <span class="cm"># 停止 WWW 服务</span></code></pre></div>
    <h3>方法二:Chocolatey</h3>
    <div class="code-block"><div class="code-header"><div class="dots"><span></span><span></span><span></span></div><span class="lang">PowerShell (管理员)</span></div>
    <pre><code>choco install nginx -y</code></pre></div>
    <h3>方法三:Scoop</h3>
    <div class="code-block"><div class="code-header"><div class="dots"><span></span><span></span><span></span></div><span class="lang">PowerShell</span></div>
    <pre><code>scoop bucket add main
scoop install nginx</code></pre></div>
    <h3>方法四:Docker Desktop</h3>
    <div class="code-block"><div class="code-header"><div class="dots"><span></span><span></span><span></span></div><span class="lang">docker-compose.yml</span></div>
    <pre><code><span class="kw">version</span>: <span class="st">"3.8"</span>
<span class="kw">services</span>:
  <span class="at">nginx</span>:
    <span class="at">image</span>: nginx:stable
    <span class="at">ports</span>: [<span class="st">"80:80"</span>, <span class="st">"443:443"</span>]
    <span class="at">volumes</span>:
      - ./html:/usr/share/nginx/html
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    <span class="at">restart</span>: unless-stopped</code></pre></div>
    <h3>注册为系统服务(NSSM)</h3>
    <div class="code-block"><div class="code-header"><div class="dots"><span></span><span></span><span></span></div><span class="lang">PowerShell (管理员)</span></div>
    <pre><code><span class="cm"># 下载: https://nssm.cc/download</span>
nssm install Nginx <span class="st">"C:\nginx\nginx.exe"</span>
nssm set Nginx AppDirectory <span class="st">"C:\nginx"</span>
nssm set Nginx Start SERVICE_AUTO_START
nssm start Nginx</code></pre></div>
    <h3>Windows 配置示例</h3>
    <div class="code-block"><div class="code-header"><div class="dots"><span></span><span></span><span></span></div><span class="lang">C:\nginx\conf\nginx.conf</span></div>
    <pre><code><span class="kw">worker_processes</span> <span class="nr">1</span>;

<span class="kw">events</span> { <span class="kw">worker_connections</span> <span class="nr">1024</span>; }

<span class="kw">http</span> {
    <span class="kw">include</span>       mime.types;
    <span class="kw">default_type</span>  application/octet-stream;
    <span class="kw">sendfile</span>  <span class="nb">on</span>;
    <span class="kw">gzip</span>     <span class="nb">on</span>;

    <span class="kw">server</span> {
        <span class="kw">listen</span> <span class="nr">80</span>;
        <span class="kw">server_name</span> localhost;

        <span class="kw">location</span> / {
            <span class="kw">root</span> html;
            <span class="kw">index</span> index.html;
        }

        <span class="kw">location</span> /api/ {
            <span class="kw">proxy_pass</span> http://<span class="nr">127.0.0.1</span>:<span class="nr">3000</span>;
            <span class="kw">proxy_set_header</span> Host $host;
            <span class="kw">proxy_set_header</span> X-Real-IP $remote_addr;
        }
    }
    <span class="kw">include</span> conf.d/*.conf;
}</code></pre></div>
    <h3>各平台安装方式对比</h3>
    <div class="table-wrap"><table>
      <thead><tr><th>平台</th><th>推荐方式</th><th>命令</th><th>场景</th></tr></thead>
      <tbody>
        <tr><td>Ubuntu/Debian</td><td>apt</td><td><code>apt install nginx</code></td><td>生产</td></tr>
        <tr><td>CentOS/RHEL</td><td>dnf</td><td><code>dnf install nginx</code></td><td>生产</td></tr>
        <tr><td>macOS</td><td>Homebrew</td><td><code>brew install nginx</code></td><td>开发</td></tr>
        <tr><td>Windows</td><td>zip</td><td>下载解压</td><td>开发</td></tr>
        <tr><td>Windows</td><td>Chocolatey</td><td><code>choco install nginx</code></td><td>开发</td></tr>
        <tr><td>Windows</td><td>Scoop</td><td><code>scoop install nginx</code></td><td>开发</td></tr>
        <tr><td>Windows</td><td>Docker</td><td><code>docker run nginx</code></td><td>一致性</td></tr>
      </tbody>
    </table></div>
  </div>

  <!-- 04 -->
  <div class="section" id="concepts">
    <span class="section-anchor"></span>
    <div class="section-header"><span class="section-number">04</span><h2>核心概念</h2></div>
    <h3>请求处理模型</h3>
    <p>Nginx 采用<strong>一个 Master 进程 + 多个 Worker 进程</strong>的模型。</p>
    <div class="diagram"><span class="highlight">Client Request</span>
   │
   ▼
┌──────────────────────────────┐
│       <span class="accent2">Master Process</span>         │
│   (读取配置, 管理 Worker)     │
└──────┬───────────┬───────────┘
       │           │
  ┌────▼────┐ ┌────▼────┐
  │<span class="highlight">Worker 1</span> │ │<span class="highlight">Worker 2</span> │
  │ (epoll) │ │ (epoll) │
  └────┬────┘ └────┬────┘
       │           │
  ┌────▼────┐ ┌────▼────┐
  │Backend A│ │Backend B│
  └─────────┘ └─────────┘</div>
    <h3>关键目录</h3>
    <div class="table-wrap"><table>
      <thead><tr><th>路径</th><th>Linux</th><th>Windows</th></tr></thead>
      <tbody>
        <tr><td>主配置</td><td><code>/etc/nginx/nginx.conf</code></td><td><code>C:\nginx\conf\nginx.conf</code></td></tr>
        <tr><td>附加配置</td><td><code>/etc/nginx/conf.d/</code></td><td><code>C:\nginx\conf.d\</code></td></tr>
        <tr><td>站点根</td><td><code>/var/www/</code></td><td><code>C:\nginx\html\</code></td></tr>
        <tr><td>日志</td><td><code>/var/log/nginx/</code></td><td><code>C:\nginx\logs\</code></td></tr>
        <tr><td>MIME</td><td><code>/etc/nginx/mime.types</code></td><td><code>C:\nginx\conf\mime.types</code></td></tr>
      </tbody>
    </table></div>
  </div>

  <!-- 05 -->
  <div class="section" id="structure">
    <span class="section-anchor"></span>
    <div class="section-header"><span class="section-number">05</span><h2>配置文件结构</h2></div>
    <div class="code-block"><div class="code-header"><div class="dots"><span></span><span></span><span></span></div><span class="lang">nginx.conf</span></div>
    <pre><code><span class="kw">user</span>              nginx;
<span class="kw">worker_processes</span>  auto;
<span class="kw">error_log</span>         /var/log/nginx/error.log <span class="nb">warn</span>;
<span class="kw">pid</span>               /var/run/nginx.pid;

<span class="kw">events</span> {
    <span class="kw">worker_connections</span> <span class="nr">1024</span>;
    <span class="kw">use</span> epoll;
}

<span class="kw">http</span> {
    <span class="kw">include</span>       /etc/nginx/mime.types;
    <span class="kw">default_type</span>  application/octet-stream;
    <span class="kw">sendfile</span>    <span class="nb">on</span>;
    <span class="kw">gzip</span>        <span class="nb">on</span>;
    <span class="kw">include</span>     /etc/nginx/conf.d/*.conf;

    <span class="kw">server</span> {
        <span class="kw">listen</span>      <span class="nr">80</span>;
        <span class="kw">server_name</span> example.com;
        <span class="kw">root</span>        /var/www/example;
        <span class="kw">location</span> /      { <span class="kw">index</span> index.html; }
        <span class="kw">location</span> /api/  { <span class="kw">proxy_pass</span> http://<span class="nr">127.0.0.1</span>:<span class="nr">3000</span>; }
    }
}</code></pre></div>
    <h3>配置层次</h3>
    <div class="diagram"><span class="highlight">main</span>        ← 全局
├── <span class="accent2">events</span>    ← 事件
└── <span class="accent2">http</span>      ← HTTP
      ├── <span class="accent2">server</span>  ← 虚拟主机
      │   └── <span class="accent2">location</span> ← URL
      └── <span class="accent2">upstream</span> ← 后端组</div>
    <div class="info-box warn"><span class="label">注意</span>
      修改配置后务必先执行 <code>nginx -t</code> 测试语法。
    </div>
  </div>

  <!-- 06 -->
  <div class="section" id="virtual-host">
    <span class="section-anchor"></span>
    <div class="section-header"><span class="section-number">06</span><h2>虚拟主机</h2></div>
    <div class="code-block"><div class="code-header"><div class="dots"><span></span><span></span><span></span></div><span class="lang">site-a.conf</span></div>
    <pre><code><span class="kw">server</span> {
    <span class="kw">listen</span> <span class="nr">80</span>;
    <span class="kw">server_name</span> site-a.com www.site-a.com;
    <span class="kw">root</span> /var/www/site-a;
    <span class="kw">location</span> / { <span class="kw">try_files</span> $uri $uri/ =<span class="nr">404</span>; }
}

<span class="cm"># 默认服务器 --- 处理未匹配请求</span>
<span class="kw">server</span> {
    <span class="kw">listen</span> <span class="nr">80</span> <span class="nb">default_server</span>;
    <span class="kw">server_name</span> _;
    <span class="kw">return</span> <span class="nr">444</span>;
}</code></pre></div>
  </div>

  <!-- 07 -->
  <div class="section" id="location">
    <span class="section-anchor"></span>
    <div class="section-header"><span class="section-number">07</span><h2>Location 路由匹配</h2></div>
    <div class="table-wrap"><table>
      <thead><tr><th>语法</th><th>类型</th><th>优先级</th></tr></thead>
      <tbody>
        <tr><td><code>= /path</code></td><td>精确匹配</td><td>最高</td></tr>
        <tr><td><code>^~ /path</code></td><td>前缀匹配</td><td>高</td></tr>
        <tr><td><code>~ pattern</code></td><td>正则(区分大小写)</td><td>中</td></tr>
        <tr><td><code>~* pattern</code></td><td>正则(不区分)</td><td>中</td></tr>
        <tr><td><code>/path</code></td><td>普通前缀</td><td>低</td></tr>
      </tbody>
    </table></div>
    <h3>实战示例</h3>
    <div class="code-block"><div class="code-header"><div class="dots"><span></span><span></span><span></span></div><span class="lang">nginx.conf</span></div>
    <pre><code><span class="kw">location</span> = /                    { <span class="kw">proxy_pass</span> http://<span class="nr">127.0.0.1</span>:<span class="nr">3000</span>/home; }
<span class="kw">location</span> ~* \.(js|css|png|jpg)$  { <span class="kw">expires</span> <span class="nr">30d</span>; <span class="kw">access_log</span> <span class="nb">off</span>; }
<span class="kw">location</span> ^~ /api/                { <span class="kw">proxy_pass</span> http://backend; }
<span class="kw">location</span> /                       { <span class="kw">try_files</span> $uri $uri/ /index.html; }
<span class="kw">location</span> ~ /\.                    { <span class="kw">deny</span> all; }</code></pre></div>
    <div class="info-box tip"><span class="label">try_files</span>
      <code>try_files $uri $uri/ /index.html</code> --- 先尝试文件 → 再尝试目录 → 都不存在返回 index.html(适合 SPA)。
    </div>
  </div>

  <!-- 08 -->
  <div class="section" id="proxy">
    <span class="section-anchor"></span>
    <div class="section-header"><span class="section-number">08</span><h2>反向代理</h2></div>
    <div class="code-block"><div class="code-header"><div class="dots"><span></span><span></span><span></span></div><span class="lang">proxy.conf</span></div>
    <pre><code><span class="kw">server</span> {
    <span class="kw">listen</span> <span class="nr">80</span>;
    <span class="kw">server_name</span> api.example.com;
    <span class="kw">location</span> / {
        <span class="kw">proxy_pass</span>         http://<span class="nr">127.0.0.1</span>:<span class="nr">3000</span>;
        <span class="kw">proxy_set_header</span>   Host $host;
        <span class="kw">proxy_set_header</span>   X-Real-IP $remote_addr;
        <span class="kw">proxy_set_header</span>   X-Forwarded-For $proxy_add_x_forwarded_for;
        <span class="kw">proxy_set_header</span>   X-Forwarded-Proto $scheme;
        <span class="kw">proxy_connect_timeout</span> <span class="nr">60s</span>;
        <span class="kw">proxy_read_timeout</span>    <span class="nr">60s</span>;
    }
}</code></pre></div>
  </div>

  <!-- 09 -->
  <div class="section" id="static">
    <span class="section-anchor"></span>
    <div class="section-header"><span class="section-number">09</span><h2>静态资源服务</h2></div>
    <div class="code-block"><div class="code-header"><div class="dots"><span></span><span></span><span></span></div><span class="lang">static.conf</span></div>
    <pre><code><span class="kw">server</span> {
    <span class="kw">listen</span> <span class="nr">80</span>;
    <span class="kw">root</span> /var/www/static;
    <span class="kw">sendfile</span> <span class="nb">on</span>;
    <span class="kw">gzip</span> <span class="nb">on</span>;
    <span class="kw">gzip_types</span> text/plain text/css application/javascript
               application/json image/svg+xml font/woff2;

    <span class="kw">location</span> ~* \.(js|css|png|jpg|svg|woff2)$ {
        <span class="kw">expires</span> <span class="nr">30d</span>;
        <span class="kw">add_header</span> Cache-Control <span class="st">"public, immutable"</span>;
    }
    <span class="kw">location</span> ~ /\. { <span class="kw">deny</span> all; }
}</code></pre></div>
  </div>

  <!-- 10 -->
  <div class="section" id="ssl">
    <span class="section-anchor"></span>
    <div class="section-header"><span class="section-number">10</span><h2>HTTPS / SSL</h2></div>
    <h3>Let's Encrypt</h3>
    <div class="code-block"><div class="code-header"><div class="dots"><span></span><span></span><span></span></div><span class="lang">bash</span></div>
    <pre><code><span class="kw">sudo</span> apt install certbot python3-certbot-nginx
<span class="kw">sudo</span> certbot --nginx -d example.com
<span class="kw">echo</span> <span class="st">"0 3 * * * certbot renew --quiet"</span> | <span class="kw">sudo</span> crontab -</code></pre></div>
    <h3>手动配置</h3>
    <div class="code-block"><div class="code-header"><div class="dots"><span></span><span></span><span></span></div><span class="lang">ssl.conf</span></div>
    <pre><code><span class="kw">server</span> {
    <span class="kw">listen</span> <span class="nr">80</span>;
    <span class="kw">server_name</span> example.com;
    <span class="kw">return</span> <span class="nr">301</span> https://$host$request_uri;
}

<span class="kw">server</span> {
    <span class="kw">listen</span> <span class="nr">443</span> ssl http2;
    <span class="kw">server_name</span> example.com;
    <span class="kw">ssl_certificate</span>      /etc/letsencrypt/live/example.com/fullchain.pem;
    <span class="kw">ssl_certificate_key</span>  /etc/letsencrypt/live/example.com/privkey.pem;
    <span class="kw">ssl_protocols</span>        TLSv1.2 TLSv1.3;
    <span class="kw">ssl_prefer_server_ciphers</span> <span class="nb">on</span>;
    <span class="kw">add_header</span> Strict-Transport-Security <span class="st">"max-age=63072000; includeSubDomains"</span> always;
    <span class="kw">root</span> /var/www/example;
}</code></pre></div>
  </div>

  <!-- 11 -->
  <div class="section" id="loadbalance">
    <span class="section-anchor"></span>
    <div class="section-header"><span class="section-number">11</span><h2>负载均衡</h2></div>
    <div class="table-wrap"><table>
      <thead><tr><th>算法</th><th>指令</th><th>说明</th></tr></thead>
      <tbody>
        <tr><td>轮询</td><td>---</td><td>按顺序分配</td></tr>
        <tr><td>加权轮询</td><td><code>weight=N</code></td><td>按权重分配</td></tr>
        <tr><td>IP Hash</td><td><code>ip_hash</code></td><td>同 IP 同后端</td></tr>
        <tr><td>最少连接</td><td><code>least_conn</code></td><td>最少连接优先</td></tr>
      </tbody>
    </table></div>
    <div class="code-block"><div class="code-header"><div class="dots"><span></span><span></span><span></span></div><span class="lang">upstream.conf</span></div>
    <pre><code><span class="kw">upstream</span> backend {
    <span class="kw">server</span> <span class="nr">10.0.0.1</span>:<span class="nr">8080</span> weight=<span class="nr">3</span>;
    <span class="kw">server</span> <span class="nr">10.0.0.2</span>:<span class="nr">8080</span> weight=<span class="nr">2</span>;
    <span class="kw">server</span> <span class="nr">10.0.0.3</span>:<span class="nr">8080</span> backup;
}

<span class="kw">server</span> {
    <span class="kw">listen</span> <span class="nr">80</span>;
    <span class="kw">location</span> / {
        <span class="kw">proxy_pass</span> http://backend;
        <span class="kw">proxy_next_upstream</span> error timeout http_502 http_503;
    }
}</code></pre></div>
  </div>

  <!-- 12 -->
  <div class="section" id="cache">
    <span class="section-anchor"></span>
    <div class="section-header"><span class="section-number">12</span><h2>缓存配置</h2></div>
    <div class="code-block"><div class="code-header"><div class="dots"><span></span><span></span><span></span></div><span class="lang">cache.conf</span></div>
    <pre><code><span class="kw">proxy_cache_path</span> /var/cache/nginx
    levels=<span class="nr">1</span>:<span class="nr">2</span> keys_zone=my_cache:<span class="nr">10m</span>
    max_size=<span class="nr">1g</span> inactive=<span class="nr">60m</span> use_temp_path=off;

<span class="kw">location</span> / {
    <span class="kw">proxy_pass</span> http://backend;
    <span class="kw">proxy_cache</span> my_cache;
    <span class="kw">proxy_cache_valid</span> <span class="nr">200</span> <span class="nr">302</span> <span class="nr">10m</span>;
    <span class="kw">proxy_cache_valid</span> <span class="nr">404</span> <span class="nr">1m</span>;
    <span class="kw">add_header</span> X-Cache-Status $upstream_cache_status;
}</code></pre></div>
  </div>

  <!-- 13 -->
  <div class="section" id="rewrite">
    <span class="section-anchor"></span>
    <div class="section-header"><span class="section-number">13</span><h2>Rewrite 规则</h2></div>
    <div class="code-block"><div class="code-header"><div class="dots"><span></span><span></span><span></span></div><span class="lang">rewrite.conf</span></div>
    <pre><code><span class="cm"># 强制 www</span>
<span class="kw">server</span> {
    <span class="kw">listen</span> <span class="nr">80</span>;
    <span class="kw">server_name</span> example.com;
    <span class="kw">return</span> <span class="nr">301</span> http://www.example.com$request_uri;
}

<span class="cm"># URL 美化</span>
<span class="kw">rewrite</span> ^/article/(\d+)$ /article.php?id=$<span class="nr">1</span> <span class="nb">last</span>;

<span class="cm"># 旧路径重定向</span>
<span class="kw">rewrite</span> ^/old-page$ /new-page <span class="nb">permanent</span>;</code></pre></div>
  </div>

  <!-- 14 -->
  <div class="section" id="websocket">
    <span class="section-anchor"></span>
    <div class="section-header"><span class="section-number">14</span><h2>WebSocket 代理</h2></div>
    <div class="code-block"><div class="code-header"><div class="dots"><span></span><span></span><span></span></div><span class="lang">websocket.conf</span></div>
    <pre><code><span class="kw">location</span> /ws/ {
    <span class="kw">proxy_pass</span> http://ws_backend;
    <span class="kw">proxy_http_version</span> <span class="nr">1.1</span>;
    <span class="kw">proxy_set_header</span> Upgrade $http_upgrade;
    <span class="kw">proxy_set_header</span> Connection <span class="st">"upgrade"</span>;
    <span class="kw">proxy_read_timeout</span> <span class="nr">3600s</span>;
    <span class="kw">proxy_buffering</span> <span class="nb">off</span>;
}</code></pre></div>
  </div>

  <!-- 15 -->
  <div class="section" id="security">
    <span class="section-anchor"></span>
    <div class="section-header"><span class="section-number">15</span><h2>安全加固</h2></div>
    <h3>安全响应头</h3>
    <div class="code-block"><div class="code-header"><div class="dots"><span></span><span></span><span></span></div><span class="lang">security.conf</span></div>
    <pre><code><span class="kw">server_tokens</span> <span class="nb">off</span>;
<span class="kw">add_header</span> X-Frame-Options <span class="st">"SAMEORIGIN"</span> always;
<span class="kw">add_header</span> X-Content-Type-Options <span class="st">"nosniff"</span> always;
<span class="kw">add_header</span> X-XSS-Protection <span class="st">"1; mode=block"</span> always;
<span class="kw">add_header</span> Referrer-Policy <span class="st">"strict-origin-when-cross-origin"</span> always;</code></pre></div>
    <h3>限流</h3>
    <div class="code-block"><div class="code-header"><div class="dots"><span></span><span></span><span></span></div><span class="lang">limit.conf</span></div>
    <pre><code><span class="kw">limit_req_zone</span> $binary_remote_addr zone=api:<span class="nr">10m</span> rate=<span class="nr">10r</span>/s;

<span class="kw">location</span> /api/ {
    <span class="kw">limit_req</span> zone=api burst=<span class="nr">20</span> nodelay;
    <span class="kw">limit_req_status</span> <span class="nr">429</span>;
    <span class="kw">proxy_pass</span> http://backend;
}</code></pre></div>
  </div>

  <!-- 16 -->
  <div class="section" id="performance">
    <span class="section-anchor"></span>
    <div class="section-header"><span class="section-number">16</span><h2>性能优化</h2></div>
    <div class="code-block"><div class="code-header"><div class="dots"><span></span><span></span><span></span></div><span class="lang">performance.conf</span></div>
    <pre><code><span class="kw">worker_processes</span> auto;
<span class="kw">worker_rlimit_nofile</span> <span class="nr">65535</span>;

<span class="kw">events</span> {
    <span class="kw">worker_connections</span> <span class="nr">10240</span>;
    <span class="kw">multi_accept</span> <span class="nb">on</span>;
    <span class="kw">use</span> epoll;
}

<span class="kw">http</span> {
    <span class="kw">sendfile</span>    <span class="nb">on</span>;
    <span class="kw">tcp_nopush</span>  <span class="nb">on</span>;
    <span class="kw">tcp_nodelay</span> <span class="nb">on</span>;
    <span class="kw">keepalive_timeout</span>  <span class="nr">65</span>;
    <span class="kw">keepalive_requests</span> <span class="nr">1000</span>;
    <span class="kw">gzip</span> <span class="nb">on</span>;
    <span class="kw">gzip_comp_level</span> <span class="nr">4</span>;
    <span class="kw">gzip_static</span> <span class="nb">on</span>;
}</code></pre></div>
  </div>

  <!-- 17 -->
  <div class="section" id="logging">
    <span class="section-anchor"></span>
    <div class="section-header"><span class="section-number">17</span><h2>日志与监控</h2></div>
    <h3>JSON 日志</h3>
    <div class="code-block"><div class="code-header"><div class="dots"><span></span><span></span><span></span></div><span class="lang">nginx.conf</span></div>
    <pre><code><span class="kw">log_format</span> json_log escape=json
    <span class="st">'{"time":"$time_iso8601","addr":"$remote_addr",'</span>
    <span class="st">'"method":"$request_method","uri":"$request_uri",'</span>
    <span class="st">'"status":$status,"time_s":$request_time}'</span>;

<span class="kw">access_log</span> /var/log/nginx/access.json json_log;</code></pre></div>
    <h3>常用变量</h3>
    <div class="table-wrap"><table>
      <thead><tr><th>变量</th><th>说明</th></tr></thead>
      <tbody>
        <tr><td><code>$remote_addr</code></td><td>客户端 IP</td></tr>
        <tr><td><code>$request_uri</code></td><td>完整 URI</td></tr>
        <tr><td><code>$status</code></td><td>状态码</td></tr>
        <tr><td><code>$request_time</code></td><td>处理时间</td></tr>
        <tr><td><code>$upstream_cache_status</code></td><td>缓存状态</td></tr>
      </tbody>
    </table></div>
  </div>

  <!-- 18 -->
  <div class="section" id="faq">
    <span class="section-anchor"></span>
    <div class="section-header"><span class="section-number">18</span><h2>常见问题</h2></div>
    <h3>Q: 配置修改后不生效?</h3>
    <p>先 <code>nginx -t</code> 测试语法,再 <code>nginx -s reload</code> 重载。</p>
    <h3>Q: 502 Bad Gateway?</h3>
    <ul>
      <li>后端服务是否运行</li>
      <li><code>proxy_pass</code> 地址端口是否正确</li>
      <li>防火墙是否放行</li>
      <li>查看 error.log</li>
    </ul>
    <h3>Q: 504 Gateway Timeout?</h3>
    <div class="code-block"><div class="code-header"><div class="dots"><span></span><span></span><span></span></div><span class="lang">nginx.conf</span></div>
    <pre><code><span class="kw">proxy_connect_timeout</span> <span class="nr">120s</span>;
<span class="kw">proxy_read_timeout</span>    <span class="nr">120s</span>;</code></pre></div>
    <h3>Q: 413 Request Entity Too Large?</h3>
    <div class="code-block"><div class="code-header"><div class="dots"><span></span><span></span><span></span></div><span class="lang">nginx.conf</span></div>
    <pre><code><span class="kw">client_max_body_size</span> <span class="nr">100m</span>;</code></pre></div>
    <h3>Q: Windows 启动失败?</h3>
    <ul>
      <li>检查 80 端口占用:<code>netstat -ano | findstr ":80"</code></li>
      <li>运行 <code>nginx -t</code> 检查配置</li>
      <li>路径中不要有中文或空格</li>
      <li>以管理员权限运行</li>
    </ul>
    <h3>Q: CORS 跨域配置?</h3>
    <div class="code-block"><div class="code-header"><div class="dots"><span></span><span></span><span></span></div><span class="lang">cors.conf</span></div>
    <pre><code><span class="kw">location</span> /api/ {
    <span class="kw">add_header</span> Access-Control-Allow-Origin $http_origin always;
    <span class="kw">add_header</span> Access-Control-Allow-Methods <span class="st">"GET, POST, PUT, DELETE, OPTIONS"</span> always;
    <span class="kw">add_header</span> Access-Control-Allow-Headers <span class="st">"Authorization, Content-Type"</span> always;
    <span class="kw">if</span> ($request_method = OPTIONS) { <span class="kw">return</span> <span class="nr">204</span>; }
    <span class="kw">proxy_pass</span> http://backend;
}</code></pre></div>

    <div class="divider"></div>
    <div class="info-box tip"><span class="label">学习资源</span>
      <ul style="margin:8px 0 0 16px">
        <li><a href="https://nginx.org/en/docs/">Nginx 官方文档</a></li>
        <li><a href="https://github.com/nginx/nginx">Nginx GitHub</a></li>
        <li><a href="https://github.com/trimstray/nginx-admins-handbook">Nginx Admin's Handbook</a></li>
        <li><a href="https://nssm.cc/">NSSM</a></li>
      </ul>
    </div>
  </div>

</main>
</div>

<script>
/* ============================================================
   1) THEME TOGGLE --- 完全独立
   ============================================================ */
(function() {
  var saved = localStorage.getItem('nginx-theme');
  if (saved) {
    document.documentElement.setAttribute('data-theme', saved);
  }
})();

function toggleTheme() {
  var html = document.documentElement;
  var next = html.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
  html.setAttribute('data-theme', next);
  localStorage.setItem('nginx-theme', next);
}

var themeBtn = document.getElementById('themeToggle');
if (themeBtn) {
  themeBtn.addEventListener('click', toggleTheme);
}

var mobileThemeBtn = document.getElementById('mobileThemeBtn');
if (mobileThemeBtn) {
  mobileThemeBtn.addEventListener('click', toggleTheme);
}

/* ============================================================
   2) SIDEBAR TOGGLE --- 完全独立
   ============================================================ */
function openSidebar() {
  var sb = document.getElementById('sidebar');
  var ov = document.getElementById('overlay');
  if (sb) sb.classList.add('open');
  if (ov) ov.classList.add('show');
}

function closeSidebar() {
  var sb = document.getElementById('sidebar');
  var ov = document.getElementById('overlay');
  if (sb) sb.classList.remove('open');
  if (ov) ov.classList.remove('show');
}

var mobileToggle = document.getElementById('mobileToggle');
if (mobileToggle) {
  mobileToggle.addEventListener('click', openSidebar);
}

var overlay = document.getElementById('overlay');
if (overlay) {
  overlay.addEventListener('click', closeSidebar);
}

/* ============================================================
   3) SCROLL SPY --- 完全独立
   ============================================================ */
(function() {
  try {
    var sections = document.querySelectorAll('.section-anchor');
    var links = document.querySelectorAll('.nav-link');

    if (!sections.length || !links.length) return;

    var observer = new IntersectionObserver(function(entries) {
      entries.forEach(function(entry) {
        if (entry.isIntersecting) {
          var parentId = entry.target.parentElement.id;
          links.forEach(function(link) {
            var href = link.getAttribute('href');
            if (href === '#' + parentId) {
              link.classList.add('active');
            } else {
              link.classList.remove('active');
            }
          });
        }
      });
    }, { rootMargin: '-80px 0px -70% 0px' });

    sections.forEach(function(s) { observer.observe(s); });

    links.forEach(function(link) {
      link.addEventListener('click', function() {
        if (window.innerWidth <= 900) {
          closeSidebar();
        }
      });
    });
  } catch (e) {
    console.warn('Scroll spy error:', e);
  }
})();

/* ============================================================
   4) TOAST --- 通知工具
   ============================================================ */
function showToast(message, type) {
  type = type || 'success';
  var toast = document.createElement('div');
  toast.className = 'toast ' + type;
  var icon = type === 'success'
    ? '\u2713'
    : '\u2398';
  toast.textContent = icon + ' ' + message;
  document.body.appendChild(toast);
  setTimeout(function() {
    toast.style.opacity = '0';
    toast.style.transition = 'opacity 0.3s';
    setTimeout(function() {
      if (toast.parentNode) toast.parentNode.removeChild(toast);
    }, 300);
  }, 2500);
}

/* ============================================================
   5) MARKDOWN EXPORT --- 独立 + 安全包装
   ============================================================ */

// 递归遍历 DOM,把一个元素转换为 Markdown
function nodeToMarkdown(node) {
  if (!node) return '';

  // 文本节点
  if (node.nodeType === 3) {
    return node.textContent || '';
  }

  // 只处理元素节点
  if (node.nodeType !== 1) return '';

  var tag = node.tagName;
  var cls = node.className || '';
  if (typeof cls !== 'string') cls = '';

  // 跳过这些
  if (cls.indexOf('section-anchor') !== -1) return '';
  if (cls.indexOf('code-header') !== -1) return '';
  if (cls.indexOf('dots') !== -1) return '';
  if (cls.indexOf('toggle-label') !== -1) return '';

  // DIV 容器
  if (tag === 'DIV') {
    if (cls.indexOf('code-block') !== -1) {
      var langEl = node.querySelector('.code-header .lang');
      var codeEl = node.querySelector('pre code');
      var lang = langEl ? langEl.textContent.trim().toLowerCase() : '';
      var code = codeEl ? codeEl.textContent.trimEnd() : '';
      return '```' + lang + '\n' + code + '\n```\n\n';
    }
    if (cls.indexOf('table-wrap') !== -1) {
      var table = node.querySelector('table');
      if (!table) return '';
      var md = '';
      var rows = table.querySelectorAll('tr');
      for (var ri = 0; ri < rows.length; ri++) {
        var cells = rows[ri].querySelectorAll('th, td');
        md += '| ';
        for (var ci = 0; ci < cells.length; ci++) {
          md += cells[ci].textContent.trim().replace(/\|/g, '\\|') + ' | ';
        }
        md += '\n';
        if (ri === 0) {
          md += '| ';
          for (var ci2 = 0; ci2 < cells.length; ci2++) { md += '--- | '; }
          md += '\n';
        }
      }
      return md + '\n';
    }
    if (cls.indexOf('info-box') !== -1) {
      var labelEl = node.querySelector('.label');
      var labelText = labelEl ? labelEl.textContent.trim() : 'Note';
      var md2 = '> **' + labelText + '**\n';
      var childNodes = node.childNodes;
      for (var i = 0; i < childNodes.length; i++) {
        var cn = childNodes[i];
        if (cn.nodeType === 3) {
          var t = cn.textContent.trim();
          if (t) md2 += '> ' + t + '\n';
        } else if (cn.nodeType === 1) {
          var ccls = cn.className || '';
          if (typeof ccls === 'string' && ccls.indexOf('label') !== -1) continue;
          if (cn.tagName === 'UL') {
            var lis = cn.querySelectorAll(':scope > li');
            for (var li = 0; li < lis.length; li++) {
              md2 += '> - ' + lis[li].textContent.trim() + '\n';
            }
          } else if (cn.tagName === 'P') {
            md2 += '> ' + cn.textContent.trim() + '\n';
          } else {
            var t2 = cn.textContent.trim();
            if (t2) md2 += '> ' + t2 + '\n';
          }
        }
      }
      return md2 + '\n';
    }
    if (cls.indexOf('diagram') !== -1) {
      return '```\n' + node.textContent.trim() + '\n```\n\n';
    }
    if (cls.indexOf('divider') !== -1) {
      return '\n---\n\n';
    }
    if (cls.indexOf('hero') !== -1) return '';
    if (cls.indexOf('hero-') !== -1) return '';
    if (cls.indexOf('section-header') !== -1) return '';
    if (cls.indexOf('sidebar') !== -1) return '';
    if (cls.indexOf('main') !== -1) {
      // 递归处理 main 的子节点
      var result = '';
      for (var m = 0; m < node.children.length; m++) {
        result += nodeToMarkdown(node.children[m]);
      }
      return result;
    }
    if (cls.indexOf('section') !== -1) {
      // 处理 section
      var sResult = '';
      var headerEl = node.querySelector('.section-header');
      if (headerEl) {
        var numEl = headerEl.querySelector('.section-number');
        var h2El = headerEl.querySelector('h2');
        if (numEl && h2El) {
          sResult += '## ' + numEl.textContent.trim() + '. ' + h2El.textContent.trim() + '\n\n';
        } else if (h2El) {
          sResult += '## ' + h2El.textContent.trim() + '\n\n';
        }
      }
      for (var s = 0; s < node.children.length; s++) {
        var child = node.children[s];
        var childCls = child.className || '';
        if (typeof childCls === 'string' && childCls.indexOf('section-anchor') !== -1) continue;
        if (typeof childCls === 'string' && childCls.indexOf('section-header') !== -1) continue;
        sResult += nodeToMarkdown(child);
      }
      sResult += '---\n\n';
      return sResult;
    }
    // 其他 div,递归子节点
    var dResult = '';
    for (var d = 0; d < node.children.length; d++) {
      dResult += nodeToMarkdown(node.children[d]);
    }
    return dResult;
  }

  // 标题
  if (tag === 'H1') return '# ' + node.textContent.trim() + '\n\n';
  if (tag === 'H2') return '## ' + node.textContent.trim() + '\n\n';
  if (tag === 'H3') return '### ' + node.textContent.trim() + '\n\n';
  if (tag === 'H4') return '#### ' + node.textContent.trim() + '\n\n';

  // 段落
  if (tag === 'P') {
    var pResult = '';
    for (var p = 0; p < node.childNodes.length; p++) {
      pResult += inlineToMarkdown(node.childNodes[p]);
    }
    return pResult.trim() + '\n\n';
  }

  // 列表
  if (tag === 'UL') {
    var ulResult = '';
    var ulItems = node.querySelectorAll(':scope > li');
    for (var ui = 0; ui < ulItems.length; ui++) {
      ulResult += '- ' + ulItems[ui].textContent.trim() + '\n';
    }
    return ulResult + '\n';
  }
  if (tag === 'OL') {
    var olResult = '';
    var olStart = parseInt(node.getAttribute('start') || '1', 10);
    var olItems = node.querySelectorAll(':scope > li');
    for (var oi = 0; oi < olItems.length; oi++) {
      olResult += (olStart + oi) + '. ' + olItems[oi].textContent.trim() + '\n';
    }
    return olResult + '\n';
  }

  // SPAN / A / STRONG 等行内元素
  var inlineResult = '';
  for (var x = 0; x < node.childNodes.length; x++) {
    inlineResult += inlineToMarkdown(node.childNodes[x]);
  }
  return inlineResult;
}

function inlineToMarkdown(node) {
  if (!node) return '';
  if (node.nodeType === 3) return node.textContent || '';
  if (node.nodeType !== 1) return '';

  var tag = node.tagName;
  var text = node.textContent || '';

  if (tag === 'STRONG' || tag === 'B') return '**' + text + '**';
  if (tag === 'CODE') return '`' + text + '`';
  if (tag === 'EM' || tag === 'I') return '*' + text + '*';
  if (tag === 'A') return '[' + text + '](' + (node.getAttribute('href') || '#') + ')';
  if (tag === 'BR') return '  \n';

  // 递归处理子节点
  var result = '';
  for (var i = 0; i < node.childNodes.length; i++) {
    result += inlineToMarkdown(node.childNodes[i]);
  }
  return result;
}

function generateMarkdown() {
  var md = '# Nginx 完全教程\n\n';
  md += '> 从零开始掌握 Nginx 的安装、配置与运维。\n\n';
  md += '---\n\n';

  // 目录
  md += '## 目录\n\n';
  var navGroups = document.querySelectorAll('.nav-group');
  for (var g = 0; g < navGroups.length; g++) {
    var groupTitle = navGroups[g].querySelector('.nav-group-title');
    if (groupTitle) md += '**' + groupTitle.textContent.trim() + '**\n\n';
    var navItems = navGroups[g].querySelectorAll('.nav-link');
    for (var n = 0; n < navItems.length; n++) {
      var text = navItems[n].textContent.trim();
      var href = navItems[n].getAttribute('href') || '';
      md += '- [' + text + '](' + href + ')\n';
    }
    md += '\n';
  }
  md += '---\n\n';

  // 正文
  var mainEl = document.querySelector('.main');
  if (mainEl) {
    var sections = mainEl.querySelectorAll(':scope > .section');
    for (var s = 0; s < sections.length; s++) {
      md += nodeToMarkdown(sections[s]);
    }
  }

  return md;
}

function exportMarkdown() {
  try {
    var md = generateMarkdown();
    var blob = new Blob([md], { type: 'text/markdown;charset=utf-8' });
    var url = URL.createObjectURL(blob);
    var a = document.createElement('a');
    a.href = url;
    a.download = 'nginx-tutorial.md';
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
    showToast('已导出 nginx-tutorial.md', 'success');
  } catch (e) {
    console.error('Export error:', e);
    showToast('导出失败: ' + e.message, 'info');
  }
}

function copyMarkdown() {
  try {
    var md = generateMarkdown();
    if (navigator.clipboard && navigator.clipboard.writeText) {
      navigator.clipboard.writeText(md).then(function() {
        showToast('已复制到剪贴板', 'info');
      }).catch(function() {
        fallbackCopy(md);
      });
    } else {
      fallbackCopy(md);
    }
  } catch (e) {
    console.error('Copy error:', e);
    showToast('复制失败', 'info');
  }
}

function fallbackCopy(text) {
  var ta = document.createElement('textarea');
  ta.value = text;
  ta.style.position = 'fixed';
  ta.style.left = '-9999px';
  document.body.appendChild(ta);
  ta.select();
  try {
    document.execCommand('copy');
    showToast('已复制到剪贴板', 'info');
  } catch (e) {
    showToast('复制失败', 'info');
  }
  document.body.removeChild(ta);
}

// 绑定按钮事件
var heroExportBtn = document.getElementById('heroExportBtn');
if (heroExportBtn) heroExportBtn.addEventListener('click', exportMarkdown);

var sidebarExportBtn = document.getElementById('sidebarExportBtn');
if (sidebarExportBtn) sidebarExportBtn.addEventListener('click', exportMarkdown);

var copyBtn = document.getElementById('copyBtn');
if (copyBtn) copyBtn.addEventListener('click', copyMarkdown);
</script>

</body>
</html>
相关推荐
大波V53 小时前
claude-code cli 跳过登录
java·服务器·前端
青山Coding3 小时前
Cesium应用(六):三维地形中坡度分析的实现过程
前端·cesium
yingyima3 小时前
Jenkins Pipeline 定时构建:两种配置方式的硬核对比!
前端
牛奶3 小时前
为什么有些网站可以像App一样离线用?
前端·pwa
Rabbit_c3 小时前
前端基于JSON Schema 配置驱动的DSL架构实践
前端
星栈3 小时前
我在 Rust 全栈项目里用 JWT 做无状态认证
前端·后端·全栈
anyup4 小时前
uni-app X 全屏引导页组件,一套支持 App、H5、小程序多端引导
前端·架构·uni-app
a1117764 小时前
动森UI组件(开源 html animal-island-ui )
前端·javascript·ui·开源·html
KaMeidebaby4 小时前
卡梅德生物技术快报|真核蛋白表达信号肽筛选实验全流程复盘
服务器·前端·数据库·人工智能·算法