HeadlessX v2.1.2 Docker Compose 部署指南

从源码构建开始,用 Docker Compose 一键拉起 HeadlessX v2.1.2 全套抓取服务(API、队列 Worker、控制台、YouTube 引擎等 7 个服务),含构建排障实录与部署后验证。

HeadlessX自托管REST API人工智能2026/10/24

HeadlessX v2.1.2 Docker Compose 部署指南

HeadlessX 是一款集成了反检测浏览器(Camoufox)、无头 Chromium、YouTube 引擎与多种搜索引擎的 AI 驱动抓取平台。它的部署形态比较特殊:官方不提供任何预构建镜像,必须从源码构建。本文记录 v2.1.2 在一台 Ubuntu 服务器上从零拉起全部 7 个服务并完成抓取 API 验证的全过程,包括构建期遇到的 GitHub 网络排障实录。

组件定位与选型分析见 《HeadlessX v2.1.2 介绍与选型指南》。

一、环境信息

项目说明
操作系统Ubuntu 22.04.4 LTS(x86_64)
CPU / 内存12 核 / 32 GB(内存建议 ≥ 8 GB,见「常见问题」)
磁盘数据盘余量 192 GB(构建产物约 17 GB,见下文镜像清单)
Docker / ComposeDocker 29.8.1 / Docker Compose v5.5.1
Git2.34.1
软件版本HeadlessX v2.1.2(仓库 main 分支,commit e3d938b)
服务器内网地址192.168.146.128
部署目录/opt/headlessx

二、镜像选型说明

HeadlessX 官方仓库自带 infra/docker/ 目录,内含 4 个 Dockerfile 与 1 份 docker-compose.yml,所有业务镜像都在本机构建,没有可替代的官方预构建镜像:

镜像(构建产物)来源 Dockerfile基础底座用途
headlessx-212-stack-apiapi.Dockerfilenode:22-slim抓取 API 主服务(含 Camoufox 浏览器束,镜像 6.61 GB)
headlessx-212-stack-workerapi.Dockerfile(同镜像复用)node:22-slim异步队列 Worker(crawl 批量任务)
headlessx-212-stack-webweb.Dockerfilenode:22-slimDashboard 控制台(Next.js 16,镜像 1.79 GB)
headlessx-212-stack-yt-engineyt-engine.Dockerfilepython:3.12-slimYouTube 引擎(yt-dlp,镜像 1.49 GB)
headlessx-212-stack-html-to-mdapps/go-html-to-md-service/DockerfileGo 1.24 构建 / alpine 运行HTML 转 Markdown 微服务(仅 28.3 MB)

基础设施部分官方 Compose 直接选用了社区维护的官方镜像,无需自建:

镜像用途
postgres:15-alpine任务日志、API Key、凭证加密存储
redis:7-alpineBullMQ 异步队列与事件流

镜像名中的 212-stack 来自 Compose 项目名 headlessx-2.1.2-stack 的规范化——Compose 会把项目名中的 . 去掉再拼进资源命名,这是预期行为,不必疑虑。

注意:api 与 worker 共用同一个镜像(api.Dockerfile),区别仅在启动命令(server_entry.ts 与 worker_entry.ts)。真正的"大头"是 api 镜像——里面打包了 713 MB 的 Camoufox 指纹浏览器束,构建机的出口网络质量会直接影响构建成败,后文有完整排障实录。

三、前置条件

  1. 服务器已安装 Docker(≥ 24)与 Docker Compose v2 插件,能正常拉取 Docker Hub 镜像;
  2. 已安装 git;
  3. 出口网络可达 GitHub(克隆仓库 + 构建期下载浏览器束),受网络限制的服务器请提前配好代理,构建参数的配法见「常见问题」Q1;
  4. 磁盘余量 ≥ 20 GB(5 个自建镜像合计约 17.5 GB,另有 713 MB 构建缓存);
  5. 若需要 YouTube 能力,服务器出口需能访问 YouTube(本环境实测不可达,表现为该模块超时,不影响其余功能)。

四、部署步骤

1. 克隆源码仓库

git clone https://github.com/saifyxpro/HeadlessX.git /opt/headlessx
cd /opt/headlessx
git log -1 --format='%H %ci'

实测输出:

e3d938b8e4563376766d114638423df058e9759d 2026-10-12 17:33:01 +0500

HeadlessX 仓库没有打版本 tag,main 分支即当前发布线。执行 grep '"version"' apps/api/package.json 确认版本号:

  "version": "2.1.2",

确认是 v2.1.2 后即可继续。

2. 准备环境变量文件

官方 Compose 从 infra/docker/.env 读取全部配置,仓库自带模板:

cd /opt/headlessx/infra/docker
cp .env.example .env

其中有两个强制必填的安全变量,必须改成自己的随机值(v2.1.2 起缺失或保留默认值会直接拒绝启动,实测见「常见问题」Q2):

openssl rand -hex 16   # 取输出填入 DASHBOARD_INTERNAL_API_KEY
openssl rand -hex 16   # 取输出填入 CREDENTIAL_ENCRYPTION_KEY

本例生成并填入:

DASHBOARD_INTERNAL_API_KEY=hx-dash-internal-b5b8f2e2453a5ab9519c57d006e446ff
CREDENTIAL_ENCRYPTION_KEY=hx-cred-enc-caa9c22ed0088895743c5bdc9e2c4c8e

⚠️ 以上是本例的真实演示值。生产环境务必用 openssl rand -hex 16 重新生成,不要照抄。

.env 其余关键项在本例中的取值(默认端口全部空闲,直接沿用官方默认):

POSTGRES_HOST_PORT=35432
REDIS_HOST_PORT=36379
HTML_TO_MARKDOWN_HOST_PORT=38081
YT_ENGINE_HOST_PORT=38090
WEB_HOST_PORT=34872
API_HOST_PORT=38473

POSTGRES_USER=postgres
POSTGRES_PASSWORD=q1w2e3r4
POSTGRES_DB=headlessx

NEXT_PUBLIC_API_URL=http://localhost:38473
FRONTEND_URL=http://localhost:34872

TAVILY_API_KEY=
EXA_API_KEY=

端口规划说明:Web 控制台 34872、API 服务 38473、PostgreSQL 35432、Redis 36379、html-to-md 38081、yt-engine 38090,均为官方默认值。若与既有服务冲突,改 Host 端口即可;其中 NEXT_PUBLIC_API_URL 若从其他机器访问控制台,需改成 http://<服务器IP>:38473。

完整变量清单与逐项解释见本专栏后续的《HeadlessX v2.1.2 配置参数详解》。

3. 预建模型目录(可选能力)

API 服务的 Compose 定义里把 apps/api/models 以绑定挂载方式挂进服务目录,作为验证码识别(reCAPTCHA 分类、目标检测)的模型目录。该目录仓库中不存在,需要手动创建,否则首次启动会由引擎自动以 root 落一个空目录:

mkdir -p /opt/headlessx/apps/api/models

验证码模型(约 108 MB 的 recaptcha_classification_57k.onnx,来源 Hugging Face)属于可选能力:不下载不影响抓取主功能,仅在遇到 reCAPTCHA 图形验证码时才需要,服务启动日志会以 ❌ Classification model missing 提示但不会失败。如需下载,在仓库根目录执行 pnpm run models:download(实为 python3 scripts/download_models.py,需要 Python 3 + requests + tqdm)。

⚠️ 实测发现官方下载脚本会把模型写到 backend/models/,而 Compose 挂载的是 apps/api/models/,两者不一致,下载完需手动拷贝到 apps/api/models/ 才会生效,详见「常见问题」Q5。

4. 构建并启动全部服务

官方 Compose 把 web/api/worker 放在 --profile all 的完整档位,启动即构建:

cd /opt/headlessx/infra/docker
docker compose --profile all up --build -d

首次构建需要下载 5 个镜像的依赖层与 713 MB 的 Camoufox 浏览器束,本例在 12 核服务器上约 25 分钟构建完成。构建成功后的尾部输出:

#65 [worker] exporting to image
#65 naming to docker.io/library/headlessx-212-stack-worker:latest done
#65 naming to docker.io/library/headlessx-212-stack-api:latest done
...(略)
✔ Container headlessx-worker         Started
✔ Container headlessx-web            Started

构建期最常见的失败点集中在 RUN pnpm exec headfox-js fetch 这一步(下载 Camoufox 浏览器束),三类实测故障与解法见「常见问题」Q1。

5. 检查服务状态

docker compose --profile all ps

实测输出(7 个服务全部 Up,带 healthcheck 的 5 个均为 healthy):

NAME                    IMAGE                              COMMAND                  SERVICE       CREATED          STATUS                    PORTS
headlessx-api           headlessx-212-stack-api:latest     "docker-entrypoint.s…"   api           2 minutes ago    Up 2 minutes (healthy)    0.0.0.0:38473->8000/tcp, [::]:38473->8000/tcp
headlessx-html-to-md    headlessx-212-stack-html-to-md:…   "/html-to-markdown"      html-to-md    2 minutes ago    Up 2 minutes (healthy)    0.0.0.0:38081->8080/tcp, [::]:38081->8080/tcp
headlessx-postgres      postgres:15-alpine                 "docker-entrypoint.s…"   postgres      2 minutes ago    Up 2 minutes (healthy)    0.0.0.0:35432->5432/tcp, [::]:35432->5432/tcp
headlessx-redis         redis:7-alpine                     "docker-entrypoint.s…"   redis         2 minutes ago    Up 2 minutes (healthy)    0.0.0.0:36379->6379/tcp, [::]:36379->6379/tcp
headlessx-web           headlessx-212-stack-web:latest     "docker-entrypoint.s…"   web           2 minutes ago    Up 2 minutes              0.0.0.0:34872->3000/tcp, [::]:34872->34872->3000/tcp
headlessx-worker        headlessx-212-stack-worker:lat…    "docker-entrypoint.s…"   worker        2 minutes ago    Up 2 minutes (healthy)
headlessx-yt-engine     headlessx-212-stack-yt-engine:l…   "python3 -m uvicorn …"   yt-engine     2 minutes ago    Up 2 minutes (healthy)    0.0.0.0:38090->8090/tcp, [::]:38090->8090/tcp

(worker 与 api 共用同一镜像,仅监听队列不对外暴露端口。)

6. 验证 API 与控制台

先看主服务健康接口:

curl -s http://localhost:38473/api/health

实测输出(已格式化截取,status 为 online 即部署成功):

{
  "status": "online",
  "timestamp": "2026-10-24T09:42:16.842Z",
  "uptime": 281.923751754,
  "version": "1.0.0",
  "engines": {
    "headfox": "available",
    "playwright": "available",
    "browser": "ready"
  },
  "capabilities": {
    "website": true,
    "search": false,
    "youtube": true
  },
  "captcha": { "enabled": false },
  "captchaModels": { "classification": false, "detection": false },
  "searchProviders": { "tavily": false, "exa": false },
  "maxConcurrency": 5,
  "currentConcurrency": 0,
  "stealthMode": "advanced",
  "proxy": { "enabled": false, "count": 0 }
}

要点:engines.browser: "ready" 说明 Camoufox 浏览器束就位;captchaModels 三项 false 是因为未下载可选模型,不影响抓取(见第 3 步说明)。

再验证控制台:

curl -s -o /dev/null -w '%{http_code}\n' http://localhost:34872/
200

浏览器访问 http://192.168.146.128:34872 即可打开 Dashboard。

7. 跑通一次真实抓取(部署验收)

控制台与 API 都依赖 API Key。先用部署期生成的内部管理键创建一个抓取用的用户键:

curl -s -X POST http://localhost:38473/api/keys \
  -H "x-api-key: hx-dash-internal-b5b8f2e2453a5ab9519c57d006e446ff" \
  -H "Content-Type: application/json" \
  -d '{"name":"ops-demo","note":"docs test key"}'

实测输出:

{"success":true,"data":{"apiKey":"hx_e9a271e7c086cb0f3ff3c2f9c49c0bbefc31fd02441f7c96","name":"ops-demo"}}

⚠️ 用户键只在创建响应中出现一次,请妥善保存。

然后用用户键抓一个页面:

curl -s -X POST http://localhost:38473/api/operators/website/scrape/html \
  -H "x-api-key: hx_e9a271e7c086cb0f3ff3c2f9c49c0bbefc31fd02441f7c96" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com"}'

实测输出(原样,html 字段为完整渲染后 HTML,此处截断展示):

{"url":"https://example.com","html":"<!DOCTYPE html><html lang=\"en\"><head>…<title>Example Domain</title>…</head><body>…<p lang=\"en\">This domain is for use in documentation examples without needing permission. This is not a service; avoid relying on it for testing and monitoring purposes.</p>…</body></html>","stats":{"duration":34166,"statusCode":200}}

返回目标页面真实渲染 HTML 与 200 状态码,部署验收完成。全部接口与命令的详细用法见本专栏《HeadlessX v2.1.2 常用 CLI 命令与工具详解》。

五、数据持久化说明

Compose 定义了 3 个命名卷承载全部持久化数据,销毁重建不会丢:

数据卷挂载点内容
headlessx-212-stack_pgdata/var/lib/postgresql/data任务日志、API Key、凭证、统计
headlessx-212-stack_browser_profile/app/apps/api/data/browser-profile指纹浏览器用户目录(Google 登录态等)
headlessx-212-stack_yt_engine_tmp/app/tmp/yt-engineYouTube 引擎的媒体文件暂存(默认保留 12 小时)

另有 1 个绑定挂载:

宿主路径挂载点说明
apps/api/models/app/apps/api/models验证码识别 ONNX 模型(可选,手动放入)

⚠️ yt_engine_tmp 只是暂存卷,引擎按 YT_ENGINE_JOB_TTL_HOURS(默认 12 小时)自动清理过期产物,不要把它当媒体仓库用。

PostgreSQL 数据备份(pg_dump 方式,容器视角在服务目录内执行):

docker compose exec postgres pg_dump -U postgres headlessx > headlessx_backup_$(date +%Y%m%d).sql

六、环境变量表

infra/docker/.env 全量变量如下(官方默认值为准,带 ★ 的为强制必填):

变量默认值说明
POSTGRES_HOST_PORT35432PostgreSQL 宿主端口
REDIS_HOST_PORT36379Redis 宿主端口
HTML_TO_MARKDOWN_HOST_PORT38081html-to-md 宿主端口
YT_ENGINE_HOST_PORT38090YouTube 引擎宿主端口
WEB_HOST_PORT34872控制台宿主端口
API_HOST_PORT38473API 服务宿主端口
WEB_PORT3000控制台内部监听端口(一般不动)
PORT8000API 内部监听端口(一般不动)
POSTGRES_USERpostgres数据库用户名
POSTGRES_PASSWORDpostgres数据库密码
POSTGRES_DBheadlessx数据库名
DATABASE_URLpostgresql://postgres:postgres@postgres:5432/headlessxAPI 数据库连接串(.env 中改了 POSTGRES_PASSWORD 时需同步)
HOST0.0.0.0API 监听地址
NODE_ENVproduction运行环境
DASHBOARD_INTERNAL_API_KEY(空)★内部管理键,控制台代理与管理面命令专用,强制必填
CREDENTIAL_ENCRYPTION_KEY(空)★凭证加密键,加密 Google/社交媒体登录态,强制必填
REDIS_URLredis://redis:6379队列连接串
TAVILY_API_KEY(空)Tavily 搜索键,未配置则搜索模块离线
EXA_API_KEY(空)Exa 搜索键,未配置则搜索模块离线
YT_ENGINE_URLhttp://yt-engine:8090YouTube 引擎地址
YT_ENGINE_TIMEOUT_MS45000引擎请求超时
YT_ENGINE_PORT8090引擎内部监听端口
YT_ENGINE_TEMP_DIR/app/tmp/yt-engine引擎暂存目录
YT_ENGINE_JOB_TTL_HOURS12产物保留时长
HTML_TO_MARKDOWN_SERVICE_URLhttp://html-to-md:8080HTML→Markdown 微服务地址
HTML_TO_MARKDOWN_TIMEOUT_MS60000微服务超时
NEXT_PUBLIC_API_URLhttp://localhost:38473控制台回连 API 的地址,远程访问需改为服务器 IP
INTERNAL_API_URLhttp://api:8000控制台内部回环地址(服务网络名,一般不动)
FRONTEND_URLhttp://localhost:34872API 侧记录的前端回调地址,远程访问需改
BULLMQ_QUEUE_NAMEheadlessx-jobs队列名
QUEUE_WORKER_CONCURRENCY2Worker 并发作业数
QUEUE_JOB_ATTEMPTS3作业失败重试次数
QUEUE_JOB_BACKOFF_MS5000重试退避毫秒
QUEUE_STREAM_POLL_MS1000事件流轮询间隔

七、配置参数说明

安全键(★ 必填):DASHBOARD_INTERNAL_API_KEY 是内部管理面钥匙(Dashboard 代理调用、/api/keys 管理命令、MCP 端点显式拒绝它);CREDENTIAL_ENCRYPTION_KEY 用于派生 AES-256-GCM 密钥加密登录态。两者留空服务直接拒绝启动(源码校验逻辑为非空检查,不设长度门槛,但从密钥强度考虑请用完整随机值)。改键后需重启 API 与 Worker 进程。

端口族:*_HOST_PORT 六项只改宿主侧映射;PORT/WEB_PORT 是服务内部监听口,除非与模板内其他服务冲突否则不要动。

队列族:QUEUE_WORKER_CONCURRENCY 决定同时跑几个 crawl 作业,每个作业占一个浏览器实例,建议 2~4;QUEUE_JOB_ATTEMPTS 与 QUEUE_JOB_BACKOFF_MS 控制失败重试节奏。这些参数修改后重启 worker 生效。

外部服务地址:YT_ENGINE_URL、HTML_TO_MARKDOWN_SERVICE_URL 默认指向 Compose 服务网络内的别名,独立部署(非 Compose)时才需要改成真实地址。

运行态参数:浏览器并发、stealth 档位、headless 开关等属于运行态配置,不走 .env,而是存库并通过控制台设置页或 PATCH /api/config 修改,即时生效。配置体系全貌见《HeadlessX v2.1.2 配置参数详解》。

八、配置文件挂载

Compose 部署模式下,配置载体有两类:

载体生效方式
infra/docker/.env由 Compose 编排层读入,注入各服务环境变量;修改后需重新编排使服务拿到新值(docker compose --profile all up -d)
运行态配置(存 PostgreSQL,经 /api/config 读写)即时生效,无需重启

注意 .env 中带"内部服务名"的变量(REDIS_URL、YT_ENGINE_URL、INTERNAL_API_URL 等)引用的是 Compose 网络内的服务别名(redis、yt-engine、api),不要改成 localhost,除非你把服务拆出了编排网络。

九、等价 docker run

Compose 模板拆开后的等价启动命令如下(生产仍建议 Compose 管理,以下仅用于理解编排关系)。基础设施两个服务可直接等价复现:

docker run -d --name headlessx-postgres \
  -e POSTGRES_USER=postgres \
  -e POSTGRES_PASSWORD=q1w2e3r4 \
  -e POSTGRES_DB=headlessx \
  -p 35432:5432 \
  -v headlessx-212-stack_pgdata:/var/lib/postgresql/data \
  postgres:15-alpine

docker run -d --name headlessx-redis \
  -p 36379:6379 \
  redis:7-alpine

api 服务等价命令(其余 worker/web/yt-engine/html-to-md 同理,改镜像名与端口即可):

docker run -d --name headlessx-api \
  --env-file /opt/headlessx/infra/docker/.env \
  -e PORT=8000 \
  -p 38473:8000 \
  -v /opt/headlessx/apps/api/models:/app/apps/api/models \
  headlessx-212-stack-api:latest \
  node apps/api/dist/server_entry.js

拆开部署需要自建网络并保证服务别名与 .env 中引用一致(postgres、redis、api、web、yt-engine、html-to-md),否则模块间调用会失败——这也是推荐 Compose 的原因。

十、常用运维命令

以下均在 /opt/headlessx/infra/docker/ 目录执行:

docker compose --profile all ps                # 全部服务状态
docker compose logs -f api                     # API 实时日志
docker compose logs -f worker                  # 队列作业日志
docker compose restart api worker              # 改 .env 安全键/队列参数后重启
docker compose --profile all stop              # 停全部服务
docker compose --profile all start             # 再启动
docker compose --profile all down              # 移除服务(保留数据卷)
docker compose --profile all up -d --build     # 升级:git pull 后重建

升级流程:

cd /opt/headlessx
git pull
cd infra/docker
docker compose --profile all up -d --build

api/worker 镜像 6.61 GB,全量重建约 25 分钟;仅改 .env 不需要 --build,up -d 即可(会重建受影响的服务)。

十一、常见问题

Q1:构建在 RUN pnpm exec headfox-js fetch 失败,报 TypeError: fetch failed / UND_ERR_CONNECT_TIMEOUT / API rate limit exceeded?

这是本例构建中最折腾的环节,三类实测故障依次是:

  1. 构建直连 GitHub:api.github.com 连接超时(UND_ERR_CONNECT_TIMEOUT,重试 5 次失败)——出口网络不可达 GitHub API;
  2. 给构建加全局代理后:GitHub API 对代理出口 IP 限流,返回 403 API rate limit exceeded for 3.115.19.172——HeadlessX 构建时会调 GitHub REST API 查询 Camoufox 发行版信息,未认证配额按出口 IP 计 60 次/小时,共享代理出口很容易被吃满;
  3. 正确解法是分流:api.github.com 查询走直连(宿主侧通常有独立的 GitHub 连通性与配额),而 713 MB 浏览器束本体(github.com 的 release 下载,CDN 分发不限流)继续走代理。通过构建参数实现:
services:
  api:
    build:
      args:
        HTTP_PROXY: http://192.168.1.62:7897
        HTTPS_PROXY: http://192.168.1.62:7897
        NO_PROXY: localhost,127.0.0.1,api.github.com

(web/worker/yt-engine/html-to-md 四个服务的 build.args 同样处理。)实测修改后一次通过。若出口可以直连 GitHub,也可全部直连、不加代理参数。

Q2:API 启动失败,日志报 Missing required security environment variables?

v2.1.2 的强制安全校验。实测把 .env 中 DASHBOARD_INTERNAL_API_KEY 清空后重启,服务立即退出:

🚀 Starting HeadlessX API Server...
[dotenv@17.2.3] injecting env (0) from .env
❌ Failed to start server: Error: Missing required security environment variables: DASHBOARD_INTERNAL_API_KEY
    at assertSecurityConfiguration (/app/apps/api/src/utils/security.ts:29:15)
    at startServer (/app/apps/api/src/server_entry.ts:14:9)

把 DASHBOARD_INTERNAL_API_KEY、CREDENTIAL_ENCRYPTION_KEY 用 openssl rand -hex 16 生成的随机值填回,重新编排后恢复(启动日志回到正常序列):

✅ Prisma migrations applied.
🚀 HeadlessX Backend running on http://0.0.0.0:8000
📂 Environment: production

Q3:控制台打不开 / 远程访问报 API 连接失败?

NEXT_PUBLIC_API_URL 默认 http://localhost:38473,从其他机器访问控制台时浏览器仍会去连访问者本机的 38473。改为 http://<服务器IP>:38473 并重新编排 web 服务。FRONTEND_URL 同理改为 http://<服务器IP>:34872。

Q4:crawl 提交后一直 pending / 503?

先看 worker 是否存活(docker compose --profile all ps,worker 是唯一不带端口映射的服务)。crawl 走 Redis 队列,worker 掉线作业会滞留队列。worker 正常时用 GET /api/jobs/metrics(带用户键)确认队列可达,实测正常返回 {"success":true,"data":{"queue":{"available":true,...}}}。

Q5:验证码模型下载后不生效?

官方 pnpm run models:download 把 ONNX 模型下载到 backend/models/,而 Compose 挂载进 API 的是 apps/api/models/(服务内 process.cwd()/models),路径不一致。下载完成后:

cp /opt/headlessx/backend/models/recaptcha_classification_57k.onnx /opt/headlessx/apps/api/models/
docker compose --profile all up -d --force-recreate api

随后 /api/health 的 captchaModels.classification 会变为 true。模型仅用于 reCAPTCHA 图形验证码识别,未配置时抓取主流程完全不受影响。

Q6:/api/operators/youtube/* 全部超时?

yt-engine 需要服务器出口能访问 YouTube。本例实测出口不可达,POST /api/operators/youtube/info 60 秒后返回 YOUTUBE_ENGINE_FAILED。该模块不可用不影响 website/search 面的任何功能;确有需要请为 yt-engine 单独配置可达出口。

Q7:宿主端口被占用怎么办?

.ENV 的六个 *_HOST_PORT 改成空闲端口即可,内部端口(PORT/WEB_PORT 及服务别名)不用动;若 API 宿主端口改了,记得同步 NEXT_PUBLIC_API_URL。

十二、参考链接