> 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/development/coding-conventions.md).

# 编码规范

这些是这个代码库的规则。它们不是可以在每个拉取请求中争论的风格偏好——它们是审查者据以检查的标准，也是让一个同时拥有两种运行时和四种语言的项目保持可读的关键。

## 语言

{% hint style="warning" %}
**仅使用 JavaScript。不要使用 TypeScript。**
{% endhint %}

新文件采用 `.js` 或 `.vue`。不 `lang="ts"` 在一个 `<script setup>` 块中，不 `.ts` 模块，不进行渐进式 TypeScript 迁移。引入 TypeScript 的 PR 会被要求移除它。

代码注释、提交信息和仓库文档使用 **英文**。区域语言包当然使用它们自己的语言。

## 函数

新函数和重写的函数使用 **`const` 箭头语法**:

{% code title="内部约定风格" %}

```js
const isValidMAC = (address) => {
    const normalized = address.replace(/[:-]/g, '');
    return normalized.length === 12 && /^[0-9A-Fa-f]+$/.test(normalized);
};

const loadSecurityChecklist = async () => { /* … */ };
```

{% endcode %}

两个注意事项：

* **对象方法保留简写语法。** `{ status(code) { … } }` 保持原样。
* **箭头 const 不会提升。** 在调用它们的代码之前先声明它们。

这适用于你编写或重写的代码。不要 **不** 批量转换现有 `函数` 声明——充满无关风格改动的 diff 比它掩盖的功能更难审查。

## 注释

按重要性排序的三条规则：

1. **每个新文件都以一段表头注释开头，说明其用途。** 对于 API 处理器来说，这意味着它的路由以及它做什么。这就是无需打开每个文件也能浏览整个代码库的方法——目录级文档刻意止步于“阅读表头注释”。
2. **大型模板和大型函数会在每个有意义的区域配上块注释。** 一个 400 行的 `.vue` 模板应该告诉你输入区域在哪里结束、结果区域在哪里开始。
3. **注释描述的是当前的代码。** 不要叙述变更历史：不要写“以前我们做 X”，不要写“这修复了……的 bug”。Git 历史已经记录了过去。说明 *为什么* 解释为什么作出某个不明显选择的注释很有价值；叙述生成它的编辑过程的注释则是噪音。

注释应当比它所解释的代码更短。

## 前端约定

完整架构见 [前端](/developer/zh/architecture/frontend.md)。编写代码时你需要遵守的规则：

* **Composition API， `<script setup>`，无处不在。** 不要使用 Options API。
* **路径别名 `@` → `frontend/`.** 导入为 `@/utils/valid-ip.js`，绝不要带着一堆 `../`.
* **先看 shadcn-vue。** 检查 `frontend/components/ui/` 是否已有现成原语，再去 shadcn-vue 目录里找一个可直接拷贝的。手写 Tailwind 是最后手段。
* **只使用语义化设计 token。** `bg-info`, `bg-action`, `text-muted-foreground`，等等——这些 token 本身会随主题变化。绝不要写 `dark:` 双配对工具类。
* **`console.*` 在前端可以。** 它仅在后端被禁止（见下文）。

### 辅助函数放哪里

这个决定几乎在每次变更中都会出现，所以有一个固定答案：

<table><thead><tr><th width="200">目录</th><th>那里放什么</th></tr></thead><tbody><tr><td><code>frontend/composables/</code></td><td>需要 Vue 响应式或生命周期的逻辑。命名为 <code>use-xxx.js</code>，导出 <code>useXxx()</code>.</td></tr><tr><td><code>frontend/utils/</code></td><td>与框架无关的辅助函数和 IO。绝不要 <code>use-</code> 加前缀。</td></tr><tr><td><code>frontend/lib/</code></td><td>仅用于 shadcn 支持层——目前只有 <code>cn()</code>。不要往里添加内容。</td></tr><tr><td><code>frontend/data/</code></td><td>静态配置和注册表：工具、章节、成就、更新日志。</td></tr></tbody></table>

一个细化说明： **与某个 composable 相邻的纯函数，会从该 composable 的文件中导出**，而不是被提升到自己的独立模块中 `utils/`. `ipFieldTone()` 拆出自 `composables/use-status-tone.js` 是值得照搬的模式。

## 共享代码位于 `common/`

双方都需要的任何东西放在 `common/` ——唯一的事实来源——，前端通过一个 **轻量的重新导出桥接层** 在 `utils/`，因此前端导入保持其熟悉的 `@/utils/...` 形式：

{% code title="frontend/utils/valid-ip.js" %}

```js
// Single source of truth is common/valid-ip.js (shared with the backend).
// This file exists as a thin re-export so front-end code can keep writing
// `import { isValidIP } from '@/utils/valid-ip.js'` without caring where
// the implementation lives.
export { isValidIP, isIPv6, isValidDomain } from '../../common/valid-ip.js';
```

{% endcode %}

`frontend/utils/fetch-with-timeout.js` 遵循相同的模式。添加桥接时，也要添加一个测试规范，导入 **两个** 路径并断言它们一致—— `tests/valid-ip.test.js` 正是这样做，才避免桥接层悄悄长出第二套实现。

## 后端约定

完整说明见 [后端](/developer/zh/architecture/backend.md)。真正有约束力的规则：

### 处理器形态

每条路由一个文件，位于 `api/`，并且只有一个默认导出：

```js
export default async (req, res) => {
    // read req.query / req.body, call upstream, write one response
};
```

错误格式简洁且一致： `400` 对于无效输入， `res.status(500).json({ error: error.message })` 当上游失败时使用。前端不会原样显示这些内容。

### 绝不用裸露的 `fetch()`

来自……的每个对外 HTTP 调用都通过 `api/` 经过 `fetchUpstream` 来自 `common/fetch-with-timeout.js`。它会应用 8 秒超时和默认 `User-Agent`。挂起的上游提供方必须超时，而不是把连接一直占着。

### 使用守卫，不要内联检查

访问控制和参数验证放在中间件（`common/guards.js`），并在……中挂载 `backend-server.js`。处理器绝不会重复实现它们：

* `requireReferer` ——全局作用于 `/api/*`
* `requireValidIP()` / `requireValidDomain()` / `requireValidPrefix()` / `requireValidASN()` / `requireValidProviderId()` / `requireValidReportId()` ——按路由单独

新的参数形态意味着要在 `common/guards.js`中新增一个守卫，而不是在处理器顶部写开放式检查。

### 日志

{% hint style="danger" %}
**`console.*` 在后端文件中被禁止。** 始终使用来自 `common/logger.js`.
{% endhint %}

Pino 以上下文优先——对象在前，简短消息在后：

{% code title="内部约定风格" %}

```js
import logger from '../common/logger.js';

logger.error({ err: e, mac: macAddress }, 'mac-checker handler failed');
logger.warn({ err: e, query }, 'whois: RDAP IP lookup failed, trying WHOIS');
```

{% endcode %}

还有两条规则：

* **处理器绝不记录“received request”这类日志行。** 按请求记录日志是 `pino-http`的职责，挂载在 `/api` 仅当 `LOG_HTTP=true`.
* **仅在启动时输出的日志行以表情符号开头** ——🚀 启动中，📦 就绪，📥 下载中，🛡️ 安全，🐢 限流，🗓️ 计划，⚠️ 可恢复，❌ 失败。按请求日志保持普通文本。

配置是 `LOG_LEVEL` （默认 `info`), `LOG_FORMAT=json` 供日志收集器使用，以及 `LOG_HTTP=true`。没有 `NODE_ENV` 在这个项目的任何地方——见 [日志](/developer/zh/configuration/logging.md).

### 边缘缓存

每个 `/api/*` 响应默认是 `Cache-Control: no-store`。 `cacheable(maxAgeSeconds)` 中间件位于 `backend-server.js`。将 TTL 写成乘法表达式（`24 * 60 * 60`），而不是原始秒数。处理器本身从不接触 `Cache-Control`，并且按用户或需要身份验证的端点绝不会被包装。

## 随你的改动一起交付的内容

有两件事不是可选项，而且都会被强制要求：

* **i18n 覆盖率。** 任何会向用户展示文案的内容都要进入 **全部四个区域版本** (`en` / `zh` / `fr` / `ru`）并在同一次改动中完成，包括 `frontend/data/changelog.json` 条目。参见 [i18n](/developer/zh/development/i18n.md).
* **测试。** 任何无需网络调用即可执行的非视觉逻辑，都要在 `tests/`中配套测试，并在同一次改动中完成。行为变化时要更新受影响的测试——不要拖延。参见 [测试](/developer/zh/development/testing.md).

然后运行 `pnpm check`。在你交付改动前，它必须是绿色。

## 下一步

* [添加新工具](/developer/zh/development/adding-a-new-tool.md) ——以上全部，端到端应用。
* [如何贡献](/developer/zh/contributing/how-to-contribute.md) ——分支、提交和 PR 期望。


---

# 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/development/coding-conventions.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.
