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

# 專案結構

儲存庫如何組織：一個 repo、兩個程序、三個層次。

MyIP 是一個會隨附兩個 Node 程序，並包含三個程式碼層的單一儲存庫 **兩個 Node 程序** 並包含 **三個程式碼層**。沒有其他了。一旦你掌握這個畫面，樹中的每個檔案都有明確的位置。

## 兩個程序

| 程序      | 檔案                   | 預設連接埠                     | 工作                                              |
| ------- | -------------------- | ------------------------- | ----------------------------------------------- |
| 靜態伺服器   | `frontend-server.js` | `18966` (`FRONTEND_PORT`) | 從 `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-tw/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`, `dns-record-types.js`, `ip-math.js`）都不含 `fs` 與 `process` 存取，並透過位於以下位置的薄橋接重新匯出： `frontend/utils/` 因此應用程式程式碼仍然從以下匯入： `@/utils/...`。只屬於 Node 的需求，若會破壞這項規則，就會放在自己的檔案中——例如上游 User-Agent 是在 `common/upstream-ua.js` （它會從磁碟讀取 `package.json` ）並注入到 `common/fetch-with-timeout.js` 於啟動時。

## 註解樹狀圖

```
.
├── backend-server.js       Express 應用程式：路由表、middleware 順序、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 資料庫、連線預設值與精選匯入清單、persona 表
│   │   └── banners/        部署時的區塊橫幅資料——已被 git 忽略（見區塊橫幅）
│   ├── components/         首頁 / StandaloneTool / sections / advanced-tools / report / widgets / ui
│   ├── composables/        具 Vue 感知的 `useXxx` 邏輯
│   ├── utils/              與框架無關的輔助工具（事件匯流排、命令匯流排、getips/、persona/、ip-calc.js、…）
│   └── locales/            每個已註冊語系各一個套件 + 按需子套件
│
├── api/                    每個路由一個處理器  → 參見後端
│
├── common/                 共用程式碼
│   ├── guards.js           參數驗證中介軟體
│   ├── dns-record-types.js 解析器會回應的 DNS 記錄類型
│   ├── fetch-with-timeout.js  fetchWithTimeout（5 秒）/ fetchUpstream（8 秒）
│   ├── ip-math.js          BigInt IP / CIDR 算術（IP 計算器 + RDAP 包含關係）
│   ├── logger.js           pino 單例
│   ├── ip-timezone.js      座標 → IANA 時區 + withTimeZone() 中介軟體
│   ├── locale-registry.js  UI 語言，以及所有由其衍生的對應
│   ├── locale-pack.js      語系套件形狀輔助工具 + 建置時剝離
│   ├── 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 + p2p 圖）
│   ├── 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 小時，其餘一切為 1 小時。
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-tw/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` (chart.js)、 `speedtest` (@cloudflare/speedtest) 與 `browser-detect` (thumbmarkjs / ua-parser-js)——並將 IP 來源與驗證輔助工具拆入 `utils-getips` / `utils-auth`.
* 字型會落在 `dist/fonts/`，其他一切都會以內容雜湊存放於 `dist/assets/`.
* 只有在以下情況下才會產生 source map： `SENTRY_AUTH_TOKEN` 已設定，且為 `隱藏` 地圖，並會從 `dist/` 在上傳後刪除——請參見 [錯誤監控](/developer/zh-tw/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-tw/getting-started/deploy-with-docker.md).

## 開發模式

`pnpm dev` 會同時啟動 Vite 與後端。Vite 會在 `FRONTEND_PORT` 本身提供 SPA（沒有 `dist/`，沒有 `frontend-server.js`），並將請求代理 `/api` 到 `BACKEND_PORT` ——因此 URL 結構與正式環境完全相同。後端會在 `nodemon` 搭配 `--import ./sentry-instrument.js`，若沒有後端 DSN 則不會有作用。設定細節見 [開發環境](/developer/zh-tw/development/dev-environment.md).

## 要在哪裡修改

| 您想要……            | 前往                                                                                                         |
| ---------------- | ---------------------------------------------------------------------------------------------------------- |
| 將工具加入 UI         | `frontend/data/tools.js` ——請參見 [新增工具](/developer/zh-tw/development/adding-a-new-tool.md)                   |
| 新增 API 路由        | 在以下位置新增一個檔案 `api/`，並在 `backend-server.js`                                                                  |
| 新增共用驗證器          | `common/`，再加上一個橋接於 `frontend/utils/` 如果瀏覽器需要它                                                              |
| 變更文案             | `frontend/locales/` ——請參見 [i18n](/developer/zh-tw/development/i18n.md)                                     |
| 在某個區塊下方顯示宣傳或贊助橫幅 | 位於以下位置的資料檔 `frontend/data/banners/` 會在建置時處理——請參見 [區塊橫幅](/developer/zh-tw/configuration/section-banners.md) |
| 新增測試             | `tests/` ——請參見 [測試](/developer/zh-tw/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-tw/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.
