> For the complete documentation index, see [llms.txt](https://docs.ipcheck.ing/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ipcheck.ing/developer/zh/reference/faq.md).

# 常见问题

下面的每个答案都描述了代码中的实际行为，而不是猜测。如果这里的内容与你的实例不符，可能是你使用了不同版本。

## 部署

<details>

<summary>每次 API 调用都会返回 403 “访问被拒绝” 或 “你在做什么？”。</summary>

全局 referer 检查拒绝了请求。有两种不同的消息：

* `{"error":"你在做什么？"}` —— 该请求携带了 **无** `Referer` 头部。
* `{"error":"访问被拒绝"}` —— 一个 `Referer` 已发送，但其主机名不被允许。

允许的主机名有 `localhost` 以及其中的所有内容 `ALLOWED_DOMAINS`。将其设置为用户实际访问网站所使用的主机名：

{% code title=".env" %}

```bash
ALLOWED_DOMAINS="myip.example.com,www.myip.example.com"
```

{% endcode %}

然后重启后端。有三点容易让人出错：

1. **匹配是精确的。** `example.com` 不包含 `sub.example.com`。请逐个列出主机名。
2. **直接用 IP 访问也算作一个主机名。** 浏览到 `http://192.168.1.10:18966` 会将该 IP 作为 referer 主机名发送——把它加进去，或者使用主机名。
3. **端口和协议无关紧要**，只比较主机名。

参见 [安全选项](/developer/zh/configuration/security-options.md).

</details>

<details>

<summary>我设置的 VITE_* 变量在重启容器后没有效果</summary>

`VITE_*` 这些变量由 Vite 在 `pnpm run build` 和 **被内联进 JavaScript 包**。它们不会在运行时读取。重启服务器无法更改已经编译进 `dist/`.

官方 `jason5ng32/myip:latest` 镜像是在没有你的值的情况下构建的—— `.env` 位于 `.dockerignore`，因此没有 `.env` 在该构建过程中也存在。传递 `-e VITE_CURL_IPV4_DOMAIN=...` 到预构建镜像对前端没有任何作用。要在 Docker 中使用构建期变量，你必须自己构建镜像。在 Node 部署中，请重新运行 `pnpm run build`.

两个 `VITE_*` 变量也会在运行时读取，并且 *会* 响应 `-e`: `VITE_SENTRY_DSN_FRONTEND` （它挂载 `/api/monitoring`）以及 `VITE_SITE_URL` （它构建上游 `User-Agent`）。完整说明见 [环境变量](/developer/zh/reference/environment-variables.md).

</details>

<details>

<summary>端口 18966 已被占用，或者我想使用不同的端口</summary>

MyIP 运行两个监听器：静态 / SPA 服务器运行在 `FRONTEND_PORT` （默认 `18966`）以及 API 服务器运行在 `BACKEND_PORT` （默认 `11966`）。前端服务器代理 `/api` 到后端，因此只需要从外部能够访问前端端口。

**Node 部署** —— 设置 `BACKEND_PORT` 和 `FRONTEND_PORT` 在 `.env` 并重启。两者都由 `backend-server.js`, `frontend-server.js` 和 `vite.config.js`读取，因此只改其中一个会导致代理失效。

**Docker** —— 不要更改容器内部端口；请改为在主机侧重新映射。镜像 `EXPOSE`s `18966`:

{% code title="shell" %}

```bash
docker run -d -p 8080:18966 --name myip --restart always jason5ng32/myip:latest
```

{% endcode %}

</details>

<details>

<summary>curl API 卡片始终不出现</summary>

这个 `curlDomainsHadSet` 中的 getter `frontend/store.js` 会将这三个域一起做 AND 运算，因此只有当 **三者都** 非空时卡片才会渲染。只设置一个或两个不会显示任何内容。一起设置 `VITE_CURL_IPV4_DOMAIN`, `VITE_CURL_IPV6_DOMAIN` 和 `VITE_CURL_IPV64_DOMAIN` —— 并且记住它们是 `VITE_*`，因此需要重新构建，而不仅仅是重启。

这些变量只提供要显示的主机名。MyIP 不提供那些端点；你需要把 DNS 记录指向你自己的纯文本 IP 回显服务。

</details>

<details>

<summary>用户收到了 429 “请求过多”</summary>

`SECURITY_RATE_LIMIT` 已设置并且客户端超过了该限制。时间窗口为 **20 分钟** 每个客户端 IP，超出后会返回 `429 {"message":"请求过多"}`.

{% hint style="info" %}
启动横幅显示 `🛡️ 已启用限流器 — 每 60 分钟 N 个请求`，但配置的窗口在 `backend-server.js` 是 `20 * 60 * 1000` 毫秒中。日志消息是错的；20 分钟才是真实窗口。
{% endhint %}

提高这个数字，或者将其设置为 `0` 以完全禁用限流器。 `SECURITY_DELAY_AFTER` 是一个独立、较温和的机制——它不会拒绝请求，只会在 N 次请求后增加 `命中次数 × 400 毫秒` 的延迟，发生在一个 **60 分钟** 的窗口内。

单次 MyIP 页面加载会触发许多 `/api` 个请求，因此较低的限制会影响普通用户。日志中会带有一个 `IP 被限流` 警告以及触发限流的 IP——每次进入限流状态时记录一次，而不是每个被阻止的请求都记录。设置 `SECURITY_BLACKLIST_LOG_FILE_PATH` ，如果你还想要一个持久化的磁盘日志。

</details>

## MaxMind

<details>

<summary>日志显示“在数据库成功加载前，MaxMind API 将返回 503”</summary>

后端无法打开 `common/maxmind-db/GeoLite2-City.mmdb` 和 `GeoLite2-ASN.mmdb`。它仍会启动，但 `GET /api/maxmind` 会返回 503，UI 中的国家徽章会一直为空。

你通常会先看到这条警告：

{% code title="日志" %}

```
⚠️  缺少 MaxMind 数据库，且未配置 MAXMIND_ACCOUNT_ID / MAXMIND_LICENSE_KEY。
  在 .env 中设置凭据并重启，或者把 GeoLite2-City.mmdb + GeoLite2-ASN.mmdb
  放入 common/maxmind-db/。仍将继续启动服务器；MaxMind API 会返回 503，直到
  数据库可用。
```

{% endcode %}

修复方法正如消息所说——设置凭据，或者预先放入这些文件：

{% code title=".env" %}

```bash
MAXMIND_ACCOUNT_ID="你的账户 ID"
MAXMIND_LICENSE_KEY="你的许可密钥"
MAXMIND_AUTO_UPDATE="true"
```

{% endcode %}

成功时看起来像 `📦 MaxMind 数据库已加载（启动）`。完整步骤见 [MaxMind 设置](/developer/zh/getting-started/maxmind-setup.md).

</details>

<details>

<summary>一个新的 Docker 容器拥有一个空的 maxmind-db 目录</summary>

这是故意的。根据 MaxMind 的许可，GeoLite2 数据库不能重新分发，而且 `.dockerignore` 排除了 `common/maxmind-db/*.mmdb` ，因此本地构建不会预置 CI 构建中没有的文件。

因此 Docker 部署者必须使用凭据方式——传入 `MAXMIND_ACCOUNT_ID`, `MAXMIND_LICENSE_KEY` 和 `MAXMIND_AUTO_UPDATE="true"` 并使用 `-e`。没有它们，容器仍会启动、提供 UI，并在 `/api/maxmind` 每次启动时对其返回 503。

第一次下载在启动期间进行，最长限制为 5 分钟。如果超时，服务器仍会继续监听——检查日志并重启。参见 [使用 Docker 部署](/developer/zh/getting-started/deploy-with-docker.md).

</details>

<details>

<summary>MAXMIND_AUTO_UPDATE 是“false”，但数据库还是下载了</summary>

按预期工作。 `MAXMIND_AUTO_UPDATE` 仅限制 **仅周期调度器**。启动时的“缺失则下载”路径不会检查它：如果存在有效凭据并且 `.mmdb` 文件缺失，则会运行一次下载周期。原因是 `.env` 中已经表达了“我希望 MaxMind 正常工作”的意图。

在使用 `MAXMIND_AUTO_UPDATE="true"` 时，你还会在启动 60 秒后进行首次检查，并且每 24 小时刷新一次。 `CAIDA_AUTO_UPDATE` 对 CAIDA 数据集的行为相同。

</details>

## 网络与 API

<details>

<summary>对 /api/... 使用 curl 返回 403，但网站在浏览器中正常工作</summary>

curl 不会发送 `Referer` 头，因此全局检查会返回 `403 {"error":"你在做什么？"}`。这不是 bug——这正是该检查的目的。

要手动测试某个端点，请提供允许的 referer：

{% code title="shell" %}

```bash
curl -H "Referer: http://localhost/" http://localhost:11966/api/configs
```

{% endcode %}

使用列在 `ALLOWED_DOMAINS` （或 `localhost`，它始终被允许）中的主机名，并直接访问后端端口。

</details>

<details>

<summary>UI 中缺少某些 IP 数据源</summary>

前端会隐藏后端没有其 API 密钥的数据源。 `GET /api/configs` 每个功能返回一个布尔值——从不返回密钥值。获取它（带上有效的 `Referer`）来准确查看你的实例认为已配置了什么： `map` 需要 `GOOGLE_MAP_API_KEY`, `ipapiis` 需要 `IPAPIIS_API_KEY`, `cloudFlare` 需要 `CLOUDFLARE_API_KEY`等等。完整字段列表在 [API 端点](/developer/zh/reference/api-endpoints.md)；键在 [可选 API 密钥](/developer/zh/configuration/optional-api-keys.md).

请注意，此路由会在边缘缓存 1 小时，因此新添加的密钥可能需要那么久才会在 CDN 后面显示出来。

</details>

<details>

<summary>/api/ipapiis 或 /api/ip2location 返回 500</summary>

这两个处理器通过调用 `.split(',')` 直接对该键调用，且没有空值检查，所以未设置的键会抛出异常并产生 500，而不是干净的错误。设置 `IPAPIIS_API_KEY` 或 `IP2LOCATION_API_KEY`.

正常使用中你不会看到这个： `/api/configs` 会将该来源报告为不可用，前端不会调用它。

</details>

<details>

<summary>报告共享返回 503 “未配置报告共享”</summary>

`POST /api/report` 和 `GET /api/report/:id` 需要 **三者都** Cloudflare Workers KV 变量： `CLOUDFLARE_API_KEY`, `CLOUDFLARE_ACCOUNT_ID` 和 `CLOUDFLARE_KV_NAMESPACE_ID`。缺少其中任何一个都会导致两个路由都返回 503，并且 `reportSharing: false` 在 `/api/configs`，这会完全隐藏共享 UI。

两个常见错误：令牌需要 **Workers KV Storage: Edit** 权限（单纯的 Radar 令牌不够），并且 `CLOUDFLARE_KV_NAMESPACE_ID` 是命名空间的 **十六进制 ID** ，而不是仪表板上的显示名称。

</details>

<details>

<summary>前端 Sentry 事件对 /api/monitoring 返回 404</summary>

`backend-server.js` 仅在以下情况下挂载隧道路由 `VITE_SENTRY_DSN_FRONTEND` 已设置 **在服务器进程上**。如果你在构建时把 DSN 烘焙进了自建镜像，但运行时没有同时传入，那么 bundle 会把 envelope 发送到一个从未挂载的路由。

两处都传入相同的值。参见 [错误监控（Sentry）](/developer/zh/configuration/error-monitoring.md).

</details>

## 开发

<details>

<summary>npm install 会破坏项目</summary>

MyIP 是 **仅支持 pnpm**。版本通过 `packageManager` 在 `package.json`, `pnpm-lock.yaml` 被提交，以及 `pnpm-workspace.yaml` 中包含原生依赖所需的安装脚本批准。npm 或 yarn 会生成冲突的锁文件，并缺少这些批准。

{% code title="shell" %}

```bash
npm install -g pnpm
pnpm install && pnpm run build
```

{% endcode %}

Dockerfile 通过以下方式做同样的事： `corepack enable`，它会提供精确锁定的 pnpm 版本。

</details>

<details>

<summary>开发服务器无法连接后端</summary>

`pnpm dev` 会同时运行 Vite 和后端。Vite 在 `FRONTEND_PORT` 提供前端，并代理 `/api` 到 `http://localhost:${BACKEND_PORT}`。如果你只改了一个端口而没有改另一个——或者只在 shell 中设置了它们，而没有在 `.env` —— 那么代理就没有指向任何东西。

两者都 `vite.config.js` 和 `backend-server.js` 从 `.env`读取相同的两个变量，因此请把它们保留在那里。参见 [开发环境](/developer/zh/development/dev-environment.md).

</details>

<details>

<summary>除错误外没有任何日志记录</summary>

`LOG_LEVEL` 下的所有内容默认使用 `info`，因此 `debug` 行会被抑制。按请求的 HTTP 日志默认完全关闭，除非你显式启用：

{% code title=".env" %}

```bash
LOG_LEVEL="debug"
LOG_HTTP="true"
```

{% endcode %}

`LOG_HTTP=true` 每个 `/api/*` 请求记录一行，包含方法、URL 和状态。它挂载在限流器之前，所以 429 也会显示。设置 `LOG_FORMAT="json"` 如果有日志收集器在消费输出。参见 [日志](/developer/zh/configuration/logging.md).

</details>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.ipcheck.ing/developer/zh/reference/faq.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
