> 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/architecture/project-structure.md).

# 项目结构

MyIP 是一个单一仓库，其中包含 **两个 Node 进程** 并包含 **三个代码层**。仅此而已。一旦你掌握了这幅图景，目录树中的每个文件都有显而易见的归属。

## 两个进程

| 进程      | 文件                   | 默认端口                      | 任务                                                      |
| ------- | -------------------- | ------------------------- | ------------------------------------------------------- |
| 静态服务器   | `frontend-server.js` | `18966` (`FRONTEND_PORT`) | 从以下位置提供构建后的 SPA： `dist/`，并将 `/api` 代理到后端，并处理 SPA 历史记录回退 |
| API 服务器 | `backend-server.js`  | `11966` (`BACKEND_PORT`)  | Express 5 应用——所有 `/api/*` 路由、守卫、速率限制、离线数据集              |

`pnpm start` 使用以下工具同时运行两者： `concurrently`。在生产环境中，它们通常由 pm2（`ecosystem.config.cjs` 定义了 `myip-frontend` 和 `myip-backend`）或 Docker 镜像管理；该镜像会运行 `npm start` 于单个容器内，并且只暴露 `18966`.

```mermaid
flowchart LR
    B["浏览器"]
    F["frontend-server.js :18966<br/>静态 dist/ + SPA 回退"]
    A["backend-server.js :11966<br/>Express 5 API"]
    U["上游提供商<br/>ipinfo.io、ip-api.com、RIPEstat、OONI"]
    D["本地数据集<br/>MaxMind mmdb、CAIDA as2org / as-rel"]

    B -->|"GET /、/tools/whois、/assets/*"| F
    B -->|"GET /api/*"| F
    F -->|"http-proxy-middleware"| A
    A -->|"fetchUpstream，8 秒超时"| U
    A --> D
```

{% hint style="info" %}
只需让前端端口可从互联网访问。后端监听于 `11966` 以供代理使用；请将其保留在同一主机或私有网络中。参见 [反向代理和域名](/developer/zh/getting-started/reverse-proxy-and-domains.md).
{% endhint %}

## 三个代码层

| 目录          | 运行位置           | 内容                                                       |
| ----------- | -------------- | -------------------------------------------------------- |
| `frontend/` | 浏览器            | Vue 3 SPA——组件、路由、Pinia store、本地化文件、工具注册表                 |
| `api/`      | Node           | 每个路由一个 Express 处理模块，别无其他                                 |
| `common/`   | Node **和** 浏览器 | 由两部分共享的代码：验证器、fetch 封装器、守卫、日志记录器、MaxMind / CAIDA 服务、报告模式 |

`common/` 是唯一跨越边界的层。浏览器也需要的模块（`valid-ip.js`, `fetch-with-timeout.js`, `report-schema.js`）不依赖于 `fs` 和 `process` 访问，并通过以下位置的轻量桥接重新导出： `frontend/utils/` 因此应用代码仍然导入 `@/utils/...`。会破坏该规则的仅 Node 端关注点存放在独立文件中——例如，上游 User-Agent 构建于 `common/upstream-ua.js` （它从磁盘读取 `package.json` ）并在启动时注入到 `common/fetch-with-timeout.js` 。

## 带注释的目录树

```
.
├── backend-server.js       Express 应用：路由表、中间件顺序、cacheable()
├── frontend-server.js      静态服务器 + /api 代理 + SPA 历史记录回退
├── sentry-instrument.js    后端 Sentry 引导，通过 `node --import` 加载
├── ecosystem.config.cjs    pm2 进程定义（携带 --import 标志）
├── index.html              Vite 入口 / SPA 外壳
├── vite.config.js          构建配置：别名、手动分块、开发代理
├── Dockerfile              两阶段构建（见下文）
│
├── frontend/               Vue 3 SPA  → 参见前端
│   ├── App.vue             轻量外壳：全局提供者 + <router-view>
│   ├── main.js             引导 + 受环境控制的动态初始化
│   ├── store.js            Pinia 主 store
│   ├── router/             路由表
│   ├── data/               静态注册表：工具、章节、成就、IP 数据库
│   ├── components/         首页 / StandaloneTool / 章节 / advanced-tools / ui
│   ├── composables/        感知 Vue 的 `useXxx` 逻辑
│   ├── utils/              框架无关的辅助工具（事件总线、getips/、…）
│   └── locales/            en / zh / fr / ru + 按需子包
│
├── api/                    每个路由一个处理程序  → 参见后端
│
├── common/                 共享代码
│   ├── guards.js           参数验证中间件
│   ├── fetch-with-timeout.js  fetchWithTimeout（5 秒）/ fetchUpstream（8 秒）
│   ├── logger.js           pino 单例
│   ├── maxmind-service.js  本地 GeoLite2 读取器 + 查找
│   ├── maxmind-updater.js  定时下载 GeoLite2
│   ├── caida-updater.js    定时下载 as2org / as-rel
│   ├── as-org-db.js        CAIDA AS → 组织查询
│   ├── as-rel-db.js        CAIDA AS 关系（p2c 图）
│   ├── service-status-*.js 提供商列表、轮询器、响应转换
│   ├── maxmind-db/         GeoLite2-City.mmdb · GeoLite2-ASN.mmdb
│   ├── as-org-db/          as-org2info.txt
│   └── as-rel-db/          as-rel2.txt
│
├── tests/                  Node 测试运行器规范（`node --test`）
└── dist/                   构建输出（自动生成，不提交）
```

## 请求如何流转

**页面加载。** 浏览器请求 `frontend-server.js` 一个 URL。

1. `/api/*` 首先由代理中间件捕获，并转发到 `http://localhost:11966/api`.
2. 否则， `express.static` 尝试从以下位置提供真实文件： `dist/`，并按以下方式应用 `Cache-Control` 响应头，按资源类别区分—— `dist/assets/**` 和 `dist/fonts/**` 获得一年加 `immutable` （Vite 会为其添加内容哈希），顶层图像为 7 天， `index.html` 和 `manifest.webmanifest` 在浏览器中为零、但在边缘节点为 24 小时，其他所有内容为一小时。
3. 如果没有文件匹配，SPA 历史记录回退将返回 `index.html` 以便 vue-router 能解析如下客户端路由： `/tools/whois`。该回退被刻意限制得很严格： `GET` 仅限， `Accept: text/html` 仅限，并且绝不适用于最后一个路径段包含点号的路径——缺失的 `/assets/x.js` 必须返回 404，而不是接收 HTML 正文。

**API 调用。** 在后端内部，请求按顺序通过中间件链： `backend-server.js` ——可选的 `pino-http`、速率限制器、slow-down、JSON 正文解析器、 `no-store` 默认设置、全局 Referer 守卫，然后是每个路由的参数守卫和处理程序。处理程序至多通过以下方式发起一次上游调用： `fetchUpstream`。详情见 [后端](/developer/zh/architecture/backend.md).

{% hint style="warning" %}
`backend-server.js` 还挂载了 `express.static('./dist')`。这方便了直接暴露后端的部署；正常路径仍然是浏览器 → 前端服务器 → 代理。
{% endhint %}

## 构建流水线

`pnpm build` 运行 Vite，后者会输出 `dist/`:

* `@` 解析为 `frontend/`.
* `manualChunks` 将重型依赖拆分为各自的块（`vendor` 用于 vue / vue-router / vue-i18n，另加 `chart`, `speedtest`, `svgmap`, `browser-detect`），并将 IP 源和身份验证辅助工具提取到 `utils-getips` / `utils-auth`.
* 字体存放在 `dist/fonts/`，其他所有内容均在以下位置按内容哈希： `dist/assets/`.
* 仅当以下变量 `SENTRY_AUTH_TOKEN` 已设置时才生成源映射，作为 `hidden` 映射，并在上传后从以下位置删除——参见 `dist/` 上传后——参见 [错误监控](/developer/zh/configuration/error-monitoring.md).

Docker 分两个阶段构建。构建阶段使用以下命令安装： `pnpm install --frozen-lockfile` 并运行 `pnpm run build`；生产阶段仅复制 `node_modules`, `package.json`, `dist/`、两个服务器文件、 `sentry-instrument.js`, `api/` 和 `common/`。没有工具链，运行时没有安装步骤。参见 [使用 Docker 部署](/developer/zh/getting-started/deploy-with-docker.md).

## 开发模式

`pnpm dev` 同时启动 Vite 和后端。Vite 在以下位置提供 SPA： `FRONTEND_PORT` 自身（没有 `dist/`，没有 `frontend-server.js`）并代理 `/api` 到 `BACKEND_PORT` ——因此 URL 形式与生产环境完全相同。后端运行于 `nodemon` 并使用 `--import ./sentry-instrument.js`，在没有后端 DSN 时它是空操作。设置详情见 [开发环境](/developer/zh/development/dev-environment.md).

## 在哪里进行修改

| 你想要……     | 访问                                                                                    |
| --------- | ------------------------------------------------------------------------------------- |
| 向 UI 添加工具 | `frontend/data/tools.js` ——参见 [添加新工具](/developer/zh/development/adding-a-new-tool.md) |
| 添加 API 路由 | 在以下位置新建文件： `api/`，并接入到 `backend-server.js`                                            |
| 添加共享验证器   | `common/`，并在以下位置添加桥接： `frontend/utils/` 如果浏览器需要它                                      |
| 修改文案      | `frontend/locales/` ——参见 [i18n](/developer/zh/development/i18n.md)                    |
| 添加测试      | `tests/` ——参见 [测试](/developer/zh/development/testing.md)                              |


---

# 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/architecture/project-structure.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.
