feat(docs): add interactive demos and complete content for development tools
- Add Vue components for interactive demos (SSH auth, regex, env vars, ports) - Complete markdown content for SSH, regex, environment variables, and ports - Remove placeholder "待实现" sections and replace with detailed guides - Add visual explanations for key concepts like ports and localhost - Include practical examples and troubleshooting tips - Add component for showing evolution from transistors to CPU - Improve documentation structure and navigation - Add security best practices for API keys and environment variables
This commit is contained in:
@@ -1,3 +0,0 @@
|
||||
# 编辑器与 AI 编程助手
|
||||
|
||||
> 待实现
|
||||
@@ -1,3 +1,179 @@
|
||||
# 环境变量与 PATH
|
||||
|
||||
> 待实现
|
||||
> 💡 **学习指南**:每次你在终端输入 `git` 或 `python`,系统都要去找这个程序在哪里。每次你的代码调用大模型 API,程序要知道用哪个密钥。这两件事背后都是同一套机制——**环境变量**。
|
||||
|
||||
---
|
||||
|
||||
## 0. 每个程序身边都带着一组配置
|
||||
|
||||
运行中的每个程序,都持有一组「键=值」配置,叫做**环境变量**。程序可以随时读取这些配置,用来了解当前的运行环境。
|
||||
|
||||
点击下方列表里的任意变量,在终端里"查看"它的值:
|
||||
|
||||
<EnvVarOverviewDemo />
|
||||
|
||||
---
|
||||
|
||||
## 1. PATH:Shell 怎么找到你输入的命令
|
||||
|
||||
`PATH` 是一个特殊的环境变量,存着一串目录路径(用冒号分隔)。你输入 `git` 时,Shell 就按这串目录的顺序,一个一个地进去找名叫 `git` 的可执行文件——找到第一个就立刻停止。
|
||||
|
||||
```bash
|
||||
$ echo $PATH
|
||||
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
|
||||
```
|
||||
|
||||
选择一个命令,观察 Shell 逐目录搜索的过程:
|
||||
|
||||
<PathSearchDemo />
|
||||
|
||||
**三个关键规律**:
|
||||
- 目录在 PATH 里越靠前,优先级越高
|
||||
- 找到第一个就停止,不会继续搜索
|
||||
- 所有目录都没有 → `command not found`
|
||||
|
||||
---
|
||||
|
||||
## 2. 为什么安装工具后要重启终端?
|
||||
|
||||
安装 nvm、Homebrew、conda 这类工具时,安装脚本会自动在 `~/.zshrc` 里追加一行,把自己的目录加入 PATH:
|
||||
|
||||
```bash
|
||||
# 安装脚本自动写入的内容(示例)
|
||||
export PATH="/usr/local/opt/python@3.12/bin:$PATH"
|
||||
```
|
||||
|
||||
这行代码只在**新 Shell 启动时**才执行。已经打开的终端窗口不受影响,所以:
|
||||
|
||||
```bash
|
||||
# 不重启也能立刻生效
|
||||
source ~/.zshrc
|
||||
```
|
||||
|
||||
**AI 开发工具常见情况**:
|
||||
|
||||
```bash
|
||||
# Ollama / pipx 装完报 command not found
|
||||
which ollama # 查实际安装位置
|
||||
|
||||
# pip 安装的 CLI 工具路径(加入 PATH)
|
||||
# macOS:~/Library/Python/3.x/bin
|
||||
# Linux:~/.local/bin
|
||||
export PATH="$PATH:$HOME/.local/bin"
|
||||
|
||||
# 推荐用 pipx 安装命令行工具,自动管理 PATH
|
||||
pipx install aider-chat
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 变量的作用域:谁能看见这个变量?
|
||||
|
||||
环境变量不是广播给所有程序的——每个进程持有**自己的一份副本**,从父进程继承而来,修改自己的副本不会影响父进程。
|
||||
|
||||
下图展示三个层级。在「用户级」里 export 一个新变量,看它是否出现在「进程级」:
|
||||
|
||||
<EnvScopeDemo />
|
||||
|
||||
---
|
||||
|
||||
## 4. export:决定子进程能不能读到这个变量
|
||||
|
||||
设置变量时,加不加 `export` 是完全不同的两件事:
|
||||
|
||||
<EnvExportDemo />
|
||||
|
||||
要让变量跨会话永久存在,把 `export` 写入配置文件:
|
||||
|
||||
```bash
|
||||
# macOS (zsh)
|
||||
echo 'export MY_VAR="value"' >> ~/.zshrc
|
||||
source ~/.zshrc # 立刻生效,不用重开终端
|
||||
|
||||
# Linux (bash)
|
||||
echo 'export MY_VAR="value"' >> ~/.bashrc
|
||||
source ~/.bashrc
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. API 密钥:绝对不能写进代码
|
||||
|
||||
调用 OpenAI、Anthropic、DeepSeek 等 API 时,密钥就是你的「身份证 + 信用卡」。泄露了,别人可以用你的额度消费,费用由你承担。
|
||||
|
||||
最常见的错误是把密钥直接写在代码里:
|
||||
|
||||
<ApiKeyDangerDemo />
|
||||
|
||||
---
|
||||
|
||||
## 6. 本地开发:用 .env 文件管密钥
|
||||
|
||||
本地开发时,把密钥放在项目根目录的 `.env` 文件里,代码通过 dotenv 库读取。`.env` 必须加入 `.gitignore`,不能提交到 Git。
|
||||
|
||||
左边写配置,右边读取——切换语言看两种写法:
|
||||
|
||||
<DotEnvDemo />
|
||||
|
||||
---
|
||||
|
||||
## 7. 生产环境:让运行平台注入密钥
|
||||
|
||||
`.env` 是开发阶段的便利工具。服务器和云平台上,应该由**运行环境**负责注入密钥,代码本身完全不感知密钥放在哪里:
|
||||
|
||||
<ServerSecretDemo />
|
||||
|
||||
---
|
||||
|
||||
## 8. 实战排错
|
||||
|
||||
### `command not found`
|
||||
|
||||
```bash
|
||||
# 第一步:确认是否在 PATH 里
|
||||
which python3 # 有输出说明找到了
|
||||
|
||||
# 第二步:找到程序实际位置(macOS)
|
||||
brew list python | grep bin
|
||||
|
||||
# 第三步:把目录加入 PATH
|
||||
export PATH="/找到的路径:$PATH"
|
||||
source ~/.zshrc # 写入配置文件后记得 source
|
||||
```
|
||||
|
||||
### 装了两个版本,用的不是我想要的
|
||||
|
||||
```bash
|
||||
which python
|
||||
# /usr/bin/python ← 系统旧版,在 PATH 靠前
|
||||
|
||||
# 把新版目录放到 PATH 最前面
|
||||
export PATH="/usr/local/bin:$PATH"
|
||||
|
||||
which python
|
||||
# /usr/local/bin/python ← 新版,现在优先了
|
||||
```
|
||||
|
||||
### 变量明明设置了,程序却读不到
|
||||
|
||||
| 原因 | 解决 |
|
||||
|:---|:---|
|
||||
| 忘了 `export` | 加上 `export` 再试 |
|
||||
| 改了 `~/.zshrc` 没生效 | `source ~/.zshrc` |
|
||||
| 用了 `.env` 但没装 dotenv | `pip install python-dotenv` / `npm install dotenv` |
|
||||
| 服务器上只在 SSH 会话有效 | 改用 systemd `EnvironmentFile` |
|
||||
|
||||
---
|
||||
|
||||
## 名词速查
|
||||
|
||||
| 术语 | 含义 |
|
||||
|:---|:---|
|
||||
| **PATH** | 存储 Shell 搜索可执行文件的目录列表,冒号分隔,顺序决定优先级 |
|
||||
| **export** | 将变量标记为可继承,子进程启动时自动获得副本 |
|
||||
| **source** | 在当前 Shell 重新执行配置文件,使修改立即生效 |
|
||||
| **which** | 显示某命令对应的可执行文件路径(PATH 搜索的结果) |
|
||||
| **.env** | 项目本地配置文件,存开发用密钥,必须加入 `.gitignore` |
|
||||
| **.env.example** | 变量名完整、值留空的模板,可以安全提交到 Git |
|
||||
| **chmod 600** | 文件权限:只有所有者可读写,适合保护密钥文件 |
|
||||
| **Secret Scanner** | GitHub 等平台自动扫描密钥泄露,发现后通知厂商吊销 |
|
||||
|
||||
@@ -1,3 +1,392 @@
|
||||
# 包管理器(npm / pip / cargo)
|
||||
# 包管理器
|
||||
|
||||
> 待实现
|
||||
> 💡 **学习指南**:写代码不必从零造轮子——99% 的功能已经有人写好并发布到互联网上了。**包管理器**就是那个帮你找到、下载并管理这些"现成零件"的工具。本章围绕一个核心问题展开:**如何让代码依赖变得可重现、可协作、可维护?**
|
||||
|
||||
---
|
||||
|
||||
## 0. 为什么你一定会用到包管理器?
|
||||
|
||||
想象你要写一个能发 HTTP 请求的 Node.js 程序。有两条路:
|
||||
|
||||
- **方法 A(手动)**:自己实现 TCP 连接、HTTP 协议解析、重定向处理、超时机制……估计要写几千行代码,调试几个月。
|
||||
- **方法 B(包管理器)**:`npm install axios`,十秒钟,一行代码搞定。
|
||||
|
||||
包管理器本质上是**代码的「应用商店」**。它帮你:
|
||||
|
||||
1. 在中央仓库(Registry)里找到别人发布的库
|
||||
2. 自动下载并安装到你的项目里
|
||||
3. 处理这个库自己依赖的其他库(依赖的依赖)
|
||||
4. 记录你用的是哪个精确版本,让团队协作不出问题
|
||||
|
||||
---
|
||||
|
||||
## 1. 各语言 / 系统生态的包管理器一览
|
||||
|
||||
不同编程语言和操作系统有各自的生态工具链,但底层逻辑完全一致。
|
||||
|
||||
👇 **动手点点看**:选择你熟悉的生态,探索它的主流包管理工具。
|
||||
|
||||
<PackageManagerOverviewDemo />
|
||||
|
||||
### 1.1 包去哪里下载?—— Registry(注册表)
|
||||
|
||||
每个生态背后都有一个中央仓库,存放所有可下载的包:
|
||||
|
||||
| 生态 | 注册表 | 包数量 |
|
||||
| :--- | :--- | :--- |
|
||||
| JavaScript | [npmjs.com](https://npmjs.com) | 200 万+ |
|
||||
| Python | [pypi.org](https://pypi.org) | 50 万+ |
|
||||
| Rust | [crates.io](https://crates.io) | 15 万+ |
|
||||
| Go | [pkg.go.dev](https://pkg.go.dev) | 50 万+ |
|
||||
| macOS/Linux 工具 | [formulae.brew.sh](https://formulae.brew.sh) | 7000+ |
|
||||
| Windows 软件 | [winget.run](https://winget.run) / [chocolatey.org](https://chocolatey.org) | 数万款 |
|
||||
|
||||
### 1.2 JavaScript 三强对比:npm vs yarn vs pnpm
|
||||
|
||||
功能相近,区别主要体现在**速度和磁盘占用**:
|
||||
|
||||
```text
|
||||
磁盘占用:pnpm(硬链接共享)< yarn PnP(零 node_modules)< npm(完整复制)
|
||||
安装速度:pnpm ≈ yarn > npm
|
||||
使用习惯:npm(最通用)> pnpm(新项目推荐)> yarn(部分团队)
|
||||
```
|
||||
|
||||
**推荐**:新项目用 `pnpm`,已有项目维持原有工具,不要随意切换。
|
||||
|
||||
### 1.3 Windows 三强对比:winget vs Chocolatey vs Scoop
|
||||
|
||||
| | winget | Chocolatey | Scoop |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **官方背书** | Microsoft 官方 | 第三方 | 第三方 |
|
||||
| **需要管理员** | 部分需要 | 是 | **不需要** |
|
||||
| **适合场景** | 日常软件安装 | 企业批量部署 | 开发工具管理 |
|
||||
| **包数量** | 多且增长快 | 最多(10000+)| 聚焦开发工具 |
|
||||
|
||||
**推荐**:日常用 `winget`,开发工具用 `scoop`,企业自动化用 `Chocolatey`。
|
||||
|
||||
---
|
||||
|
||||
## 2. 安装包 —— 背后发生了什么?
|
||||
|
||||
输入 `npm install axios` 后,命令行安静了几秒,然后就好了。这几秒里到底发生了什么?
|
||||
|
||||
👇 **动手点点看**:选择一个包,点击"运行",观察安装的全过程。
|
||||
|
||||
<PackageInstallDemo />
|
||||
|
||||
### 2.1 四个阶段详解
|
||||
|
||||
**① 依赖解析(Resolve)**
|
||||
|
||||
包管理器先"读懂"你要装什么。以 `axios` 为例,它自己依赖 `follow-redirects`、`form-data` 等包,这些也都要安装。这个过程叫做**构建依赖树**。
|
||||
|
||||
**② 下载(Fetch)**
|
||||
|
||||
从 Registry 下载所有需要的包(`.tgz` 格式的压缩包)。聪明的包管理器会:
|
||||
- 并行下载多个包,而不是一个个等待
|
||||
- 先查本地缓存,命中就不走网络
|
||||
|
||||
**③ 链接(Link)**
|
||||
|
||||
把下载的包解压放到 `node_modules/` 目录,并处理好引用关系。
|
||||
|
||||
**④ 写锁文件(Lockfile)**
|
||||
|
||||
把这次安装的**精确版本号**写入 `package-lock.json`(或 `yarn.lock` / `pnpm-lock.yaml`)。
|
||||
|
||||
### 2.2 最常用命令速查
|
||||
|
||||
```bash
|
||||
# ── JavaScript (npm) ──────────────────────────────────
|
||||
npm install # 按 package.json 安装所有依赖
|
||||
npm install axios # 安装新包(生产依赖)
|
||||
npm install -D jest # 安装开发依赖(只在开发时用)
|
||||
npm install -g tsx # 全局安装(任何目录都能用)
|
||||
npm uninstall axios # 卸载包
|
||||
npm update # 升级所有包到兼容的最新版
|
||||
npm run build # 运行 package.json scripts 里的脚本
|
||||
npx create-react-app . # 临时运行,不安装到项目
|
||||
|
||||
# ── Python (pip) ──────────────────────────────────────
|
||||
pip install requests # 安装包
|
||||
pip install requests==2.28.0 # 安装指定版本
|
||||
pip freeze > requirements.txt # 导出当前依赖列表
|
||||
pip install -r requirements.txt # 按列表安装
|
||||
|
||||
# ── Rust (cargo) ──────────────────────────────────────
|
||||
cargo add serde # 添加依赖(会自动更新 Cargo.toml)
|
||||
cargo build # 构建项目
|
||||
cargo test # 运行测试
|
||||
cargo run # 运行项目
|
||||
|
||||
# ── Go (go mod) ───────────────────────────────────────
|
||||
go get github.com/gin-gonic/gin # 添加依赖
|
||||
go mod tidy # 整理依赖(删多余、补缺失)
|
||||
go build ./... # 构建
|
||||
|
||||
# ── Windows (winget) ──────────────────────────────────
|
||||
winget install Git.Git # 安装软件
|
||||
winget upgrade --all # 更新所有已安装软件
|
||||
```
|
||||
|
||||
### 2.3 npm scripts 是什么?
|
||||
|
||||
`package.json` 里有一个 `scripts` 字段,这是 npm 内置的**任务运行器**:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"test": "jest",
|
||||
"lint": "eslint src/"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
运行方式:`npm run dev`、`npm run build`。这样做的好处是:
|
||||
- **统一入口**:团队成员不需要记住底层工具的具体命令
|
||||
- **环境自动配置**:运行时会自动把 `node_modules/.bin` 加入 PATH,可以直接用本地安装的工具
|
||||
|
||||
---
|
||||
|
||||
## 3. 全局安装 vs 本地安装
|
||||
|
||||
这是新手最容易困惑的概念之一。
|
||||
|
||||
### 3.1 两者的区别
|
||||
|
||||
```bash
|
||||
npm install axios # 本地安装:装到 ./node_modules/,只有当前项目能用
|
||||
npm install -g typescript # 全局安装:装到系统目录,任何项目/目录都能用
|
||||
```
|
||||
|
||||
| | 本地安装 | 全局安装 |
|
||||
| :--- | :--- | :--- |
|
||||
| **存放位置** | `./node_modules/` | 系统级目录(如 `/usr/local/lib/`) |
|
||||
| **适合** | 项目依赖的库(axios、vue、react) | 命令行工具(tsc、eslint、create-react-app) |
|
||||
| **版本隔离** | 每个项目独立版本 ✅ | 全机共用一个版本 ⚠️ |
|
||||
| **团队一致性** | 锁文件保证一致 ✅ | 各人版本可能不同 ⚠️ |
|
||||
|
||||
### 3.2 黄金法则
|
||||
|
||||
> **库类依赖(axios、lodash、vue)永远本地安装;
|
||||
> 命令行工具(tsc、eslint)优先本地安装,用 `npx` 调用。**
|
||||
|
||||
**为什么命令行工具也推荐本地安装?**
|
||||
|
||||
假设你全局安装了 `eslint@8`,但项目 A 需要 `eslint@9` 的新规则,你就要在全局和项目之间反复切换。把 `eslint` 装到本地,用 `npx eslint .` 调用,每个项目都能独立配置自己的版本。
|
||||
|
||||
### 3.3 npx —— 临时运行,不污染环境
|
||||
|
||||
`npx` 是 npm 自带的工具运行器,允许你**不安装直接运行**一个包:
|
||||
|
||||
```bash
|
||||
# 不安装 create-vue,直接运行它来初始化项目
|
||||
npx create-vue my-project
|
||||
|
||||
# 不安装 prettier,直接格式化文件
|
||||
npx prettier --write src/
|
||||
|
||||
# 强制使用指定版本(忽略已安装的)
|
||||
npx typescript@5.4 tsc --version
|
||||
```
|
||||
|
||||
Python 的 `uvx`、Rust 的 `cargo run` 也提供了类似的"临时运行"能力:
|
||||
|
||||
```bash
|
||||
uvx ruff check . # Python:临时运行 ruff 检查器
|
||||
cargo install ripgrep # Rust:安装到全局,变成系统命令 rg
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 版本号的秘密 —— 语义化版本
|
||||
|
||||
你在 `package.json` 里会看到这样的内容:
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"axios": "^1.6.8",
|
||||
"typescript": "~5.4.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这里的 `^` 和 `~` 是什么意思?
|
||||
|
||||
👇 **动手点点看**:鼠标悬停版本号各个部分,理解含义;点击范围符号,看哪些版本会被接受。
|
||||
|
||||
<DependencyTreeDemo />
|
||||
|
||||
### 4.1 为什么不锁死版本?
|
||||
|
||||
| 做法 | 优点 | 缺点 |
|
||||
| :--- | :--- | :--- |
|
||||
| `"axios": "1.6.8"`(精确锁定) | 完全可预测 | 安全补丁无法自动更新 |
|
||||
| `"axios": "^1.6.8"`(兼容范围,推荐) | 自动获取 bug 修复和新功能 | 极少情况下引入小不兼容 |
|
||||
| `"axios": "*"`(任意版本) | 总是最新 | 主版本升级会彻底破坏代码 |
|
||||
|
||||
**最佳实践**:用 `^` 声明范围 + 锁文件固定实际版本,两者配合使用。
|
||||
|
||||
### 4.2 依赖地狱是什么?
|
||||
|
||||
当你依赖 50 个包,每个包又依赖若干包,"依赖树"可能有几百个节点。如果两个你依赖的包需要**同一个库的不兼容版本**,就产生了"依赖冲突"。
|
||||
|
||||
各生态的解法:
|
||||
- **npm v3+**:同主版本提升到顶层共享,不同主版本各自安装一份
|
||||
- **pnpm**:硬链接 + 严格隔离,从根本上防止"幽灵依赖"(没声明却能用的包)
|
||||
- **cargo(Rust)**:语言层面强制每个包只能依赖同一版本,彻底规避冲突
|
||||
- **go mod(Go)**:最小版本选择(MVS)策略,选能满足所有约束的最低版本
|
||||
|
||||
---
|
||||
|
||||
## 5. 锁文件 —— 团队协作的基石
|
||||
|
||||
### 5.1 为什么需要锁文件?
|
||||
|
||||
假设 `package.json` 写的是 `"axios": "^1.6.0"`:
|
||||
|
||||
- 你今天安装 → 装到 `1.6.8`
|
||||
- 队友明天安装 → 可能装到 `1.7.0`(昨晚刚发布)
|
||||
- CI 服务器下周 → 可能装到 `1.7.1`
|
||||
|
||||
同样的代码,三个人跑出不同结果。**锁文件**记录每个包的精确版本,所有人按它安装,结果完全一致。
|
||||
|
||||
| 场景 | 命令 | 行为 |
|
||||
| :--- | :--- | :--- |
|
||||
| 开发环境同步 | `npm install` | 参考锁文件安装,不升级版本 |
|
||||
| CI / 生产部署 | `npm ci` | **严格**按锁文件安装,有差异直接报错 |
|
||||
| 主动升级版本 | `npm update` | 在允许范围内升级,并更新锁文件 |
|
||||
|
||||
### 5.2 锁文件应该提交到 Git 吗?
|
||||
|
||||
**应用程序必须提交,发布到 npm 的库可以不提交。**
|
||||
|
||||
- ✅ **Web 应用、后端服务**:必须提交,确保部署环境和开发环境完全一致
|
||||
- ❌ **npm 发布的库**:通常不提交,库的使用者有自己的锁文件
|
||||
- ✅ **Python 项目**:`requirements.txt` 本身就起锁文件作用,应该提交
|
||||
- ✅ **Go 项目**:`go.sum` 必须提交,用于完整性校验
|
||||
|
||||
---
|
||||
|
||||
## 6. Python 虚拟环境
|
||||
|
||||
Python 有一个特别需要注意的概念:**虚拟环境(venv)**。
|
||||
|
||||
**为什么需要?**
|
||||
|
||||
Python 默认**全局**安装包。你的项目 A 需要 `requests==2.28`,项目 B 需要 `requests==2.31`,两者会互相冲突。
|
||||
|
||||
**解决方案**:为每个项目创建独立的虚拟环境,互不干扰。
|
||||
|
||||
```bash
|
||||
# 1. 创建虚拟环境(在项目根目录运行)
|
||||
python -m venv .venv
|
||||
|
||||
# 2. 激活虚拟环境
|
||||
source .venv/bin/activate # macOS / Linux
|
||||
.venv\Scripts\activate # Windows(命令提示符 CMD)
|
||||
.venv\Scripts\Activate.ps1 # Windows(PowerShell)
|
||||
|
||||
# 3. 激活后,pip install 只影响当前虚拟环境,不污染全局
|
||||
pip install requests
|
||||
|
||||
# 4. 退出虚拟环境
|
||||
deactivate
|
||||
```
|
||||
|
||||
> ⚠️ **Windows 常见问题**:PowerShell 默认禁止运行脚本,需先执行:
|
||||
> ```powershell
|
||||
> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||
> ```
|
||||
|
||||
**现代替代方案**:
|
||||
- `conda create -n myproject python=3.11` —— 连 Python 版本都一起管理
|
||||
- `uv venv && source .venv/bin/activate` —— Rust 写的,创建速度飞快
|
||||
|
||||
**`.venv` 要提交到 Git 吗?**
|
||||
|
||||
不要!`.venv` 是本机生成的,应加入 `.gitignore`。用 `requirements.txt` 或 `pyproject.toml` 来描述依赖。
|
||||
|
||||
---
|
||||
|
||||
## 7. 常见问题速查
|
||||
|
||||
**Q: `node_modules` 要提交到 Git 吗?**
|
||||
|
||||
不要!通常有几百 MB,应该加入 `.gitignore`。有了 `package-lock.json`,任何人都能 `npm install` 快速重建。
|
||||
|
||||
**Q: 安装失败 / 出现奇怪报错怎么办?**
|
||||
|
||||
```bash
|
||||
# 清空缓存,删除旧安装,重来
|
||||
npm cache clean --force
|
||||
rm -rf node_modules package-lock.json # macOS/Linux
|
||||
rmdir /s /q node_modules && del package-lock.json # Windows CMD
|
||||
npm install
|
||||
```
|
||||
|
||||
**Q: 安装速度太慢?**
|
||||
|
||||
```bash
|
||||
# 切换到国内镜像(推荐写入 .npmrc 文件,不污染全局)
|
||||
echo "registry=https://registry.npmmirror.com" > .npmrc
|
||||
|
||||
# pip 也可以配置镜像
|
||||
pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple
|
||||
```
|
||||
|
||||
**Q: 包有安全漏洞怎么处理?**
|
||||
|
||||
```bash
|
||||
npm audit # 扫描已知漏洞
|
||||
npm audit fix # 自动修复兼容的漏洞
|
||||
npm audit fix --force # 强制升级(可能有破坏性,谨慎用)
|
||||
```
|
||||
|
||||
**Q: 怎么知道某个包是否值得信赖?**
|
||||
|
||||
在 [npmjs.com](https://npmjs.com) 或 [bundlephobia.com](https://bundlephobia.com) 查看:
|
||||
- 周下载量(越高越可信)
|
||||
- 最后更新时间(超过 2 年没更新要谨慎)
|
||||
- 依赖数量(依赖越多,引入问题的可能性越大)
|
||||
- GitHub Stars 和 Issues 活跃度
|
||||
|
||||
**Q: Windows 上 winget 安装的软件在哪?**
|
||||
|
||||
winget 默认安装到系统目录(需要管理员)或 `%LOCALAPPDATA%\Microsoft\WindowsApps`。Scoop 安装的软件统一在 `%USERPROFILE%\scoop\apps\`,方便管理和迁移。
|
||||
|
||||
---
|
||||
|
||||
## 8. 名词对照表
|
||||
|
||||
| 英文术语 | 中文对照 | 解释 |
|
||||
| :--- | :--- | :--- |
|
||||
| **Package** | 包 / 库 | 别人写好并发布的代码模块 |
|
||||
| **Registry** | 注册表 / 仓库 | 所有包的中央存储服务器(如 npmjs.com) |
|
||||
| **Dependency** | 依赖 | 你的项目运行所需要的其他包 |
|
||||
| **devDependency** | 开发依赖 | 只在开发阶段需要的包(测试框架、构建工具等) |
|
||||
| **Lockfile** | 锁文件 | 记录精确版本号,保证环境一致性 |
|
||||
| **SemVer** | 语义化版本 | MAJOR.MINOR.PATCH 版本命名规范 |
|
||||
| **node_modules** | 模块目录 | npm 安装的包实际存放的目录 |
|
||||
| **venv** | 虚拟环境 | Python 项目的独立包隔离沙箱 |
|
||||
| **tarball** | 压缩包 | 包的分发格式,通常为 `.tgz` 文件 |
|
||||
| **Hoisting** | 提升 | npm 将子依赖提升到顶层以避免重复安装 |
|
||||
| **Phantom Dependency** | 幽灵依赖 | 未在配置文件声明却能被使用的包(pnpm 可防止) |
|
||||
| **npx** | — | npm 自带的包运行器,临时运行包而无需安装 |
|
||||
| **go.sum** | — | Go 模块的哈希校验文件,防止依赖被篡改 |
|
||||
| **Crate** | — | Rust 生态中"包"的单位名称 |
|
||||
| **winget** | — | Windows 官方包管理器(Windows 10/11 内置) |
|
||||
|
||||
---
|
||||
|
||||
## 总结:包管理器的本质
|
||||
|
||||
四句话记住核心:
|
||||
|
||||
1. **包管理器 = 应用商店**:帮你找到、安装、管理代码零件,不必重复造轮子。
|
||||
2. **锁文件 = 团队契约**:固定精确版本,让"在我机器上好好的"成为历史。
|
||||
3. **语义化版本 = 沟通语言**:`^` 安全地获取更新,MAJOR 变了就要小心。
|
||||
4. **本地 > 全局**:项目依赖尽量本地安装,`npx` / `uvx` 临时运行工具,保持环境纯净。
|
||||
|
||||
@@ -1,3 +1,248 @@
|
||||
# 端口与 localhost
|
||||
|
||||
> 待实现
|
||||
> 💡 **学习指南**:当你执行 `npm run dev`,终端里出现 `http://localhost:5173` 时,你有没有想过:`localhost` 是什么?`5173` 又代表什么?为什么有时候会报 `EADDRINUSE` 错误?本章就来把这些日常开发中天天见、却很少深究的概念一次讲透。
|
||||
|
||||
在开始之前,建议你先补两块"基础砖":
|
||||
|
||||
- **网络基础**:如果你不太清楚 IP 地址和 HTTP 的概念,可以先看 [计算机基础 - 网络通信](../1-computer-fundamentals/network-fundamentals.md) 部分。
|
||||
- **终端基础**:如果你还不熟悉终端命令行,可以先看 [命令行与 Shell 脚本](./command-line-shell.md)。
|
||||
|
||||
---
|
||||
|
||||
## 0. 引言:那个天天见的 `localhost:5173` 到底是什么?
|
||||
|
||||
<DevServerFlowDemo />
|
||||
|
||||
每个开发者的日常都离不开这一行输出:
|
||||
|
||||
```
|
||||
➜ Local: http://localhost:5173/
|
||||
```
|
||||
|
||||
但你有没有想过,这短短一行字里,藏着好几个关键概念:
|
||||
|
||||
- **http://** → 通信协议(用什么语言对话)
|
||||
- **localhost** → 目标地址(找谁)
|
||||
- **:5173** → 端口号(找到之后,敲哪扇门)
|
||||
|
||||
搞懂这三件事,你就能理解 90% 的开发环境网络问题。接下来我们逐个拆解。
|
||||
|
||||
---
|
||||
|
||||
## 1. 什么是端口?(IP 是大楼,端口是房间号)
|
||||
|
||||
### 1.1 一个直觉比喻
|
||||
|
||||
想象一台服务器是一栋大楼:
|
||||
|
||||
- **IP 地址**(如 `192.168.1.100`)就是大楼的门牌地址——告诉你"去哪栋楼"。
|
||||
- **端口号**(如 `:80`)就是楼里的房间号——告诉你"进哪间房"。
|
||||
|
||||
一栋楼里可以同时有餐厅(80 号房)、咖啡厅(443 号房)、办公室(22 号房)。同理,一台电脑上可以同时运行 Web 服务器、数据库、SSH 服务,各自占用不同的端口。
|
||||
|
||||
👇 **动手点点看**:
|
||||
点击下面的"房间门牌",模拟向不同端口发起连接。注意观察:当端口"开着"(有程序在监听)和"关着"时,分别会发生什么?
|
||||
|
||||
<PortAnalogyDemo />
|
||||
|
||||
### 1.2 端口号的取值范围
|
||||
|
||||
端口号是一个 **0–65535** 之间的整数(共 65536 个)。这么多端口被分为三个区间:
|
||||
|
||||
| 区间 | 范围 | 用途 | 举例 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **系统端口** | 0 – 1023 | 预留给标准协议,普通用户不能随意占用 | 80 (HTTP)、443 (HTTPS)、22 (SSH) |
|
||||
| **注册端口** | 1024 – 49151 | 给常见应用注册使用 | 3306 (MySQL)、5432 (PostgreSQL)、6379 (Redis) |
|
||||
| **动态端口** | 49152 – 65535 | 操作系统临时分配 | 浏览器发请求时,系统随机分配一个源端口 |
|
||||
|
||||
> 为什么你的开发服务器喜欢用 3000、5173、8080?因为这些都在"注册端口"范围内,不需要管理员权限就能监听,又不太容易和系统服务冲突。
|
||||
|
||||
### 1.3 开发中常见的端口号速查
|
||||
|
||||
👇 **动手点点看**:
|
||||
输入端口号或服务名搜索,点击任意一行可以展开查看使用示例。
|
||||
|
||||
<CommonPortsDemo />
|
||||
|
||||
---
|
||||
|
||||
## 2. 什么是 localhost?(自己找自己)
|
||||
|
||||
### 2.1 "环回"的核心概念
|
||||
|
||||
`localhost` 是一个特殊的域名,它永远指向**你自己这台电脑**。
|
||||
|
||||
当你在浏览器输入 `http://localhost:3000` 时,发生了这些事:
|
||||
|
||||
1. 浏览器问操作系统:"`localhost` 的 IP 是多少?"
|
||||
2. 操作系统直接回答:"`127.0.0.1`"(不需要联网查 DNS)
|
||||
3. 数据包发往 `127.0.0.1`,但**不会真的离开本机**
|
||||
4. 操作系统通过"环回接口(loopback interface)"把数据包**折返**回来
|
||||
5. 监听在 3000 端口上的程序收到请求,返回响应
|
||||
|
||||
**整个过程不经过网线、不经过路由器、不需要联网。**
|
||||
|
||||
👇 **动手点点看**:
|
||||
点击"发送请求",观察数据包的完整旅程。然后点击下方的"马甲卡片",了解 localhost 的几种写法和区别。
|
||||
|
||||
<LocalhostLoopbackDemo />
|
||||
|
||||
### 2.2 `localhost` vs `127.0.0.1` vs `0.0.0.0`
|
||||
|
||||
这三个概念经常被混淆,但它们的含义完全不同:
|
||||
|
||||
| 写法 | 含义 | 谁能访问 |
|
||||
| :--- | :--- | :--- |
|
||||
| `localhost` / `127.0.0.1` | 环回地址,仅本机 | 只有你自己的电脑 |
|
||||
| `0.0.0.0` | 监听所有网络接口 | 本机 + 局域网内其他设备 |
|
||||
| `192.168.x.x` | 局域网 IP | 局域网内的设备 |
|
||||
|
||||
**实际场景**:
|
||||
|
||||
```bash
|
||||
# 只有自己能访问(安全,适合开发)
|
||||
npm run dev -- --host localhost
|
||||
|
||||
# 手机也能访问(适合移动端调试)
|
||||
npm run dev -- --host 0.0.0.0
|
||||
```
|
||||
|
||||
> 很多框架(如 Vite、Next.js)默认监听 `localhost`,所以你的手机即使连着同一个 WiFi 也访问不了。想用手机调试?加上 `--host` 参数就行。
|
||||
|
||||
---
|
||||
|
||||
## 3. 端口冲突:最常见的开发环境问题
|
||||
|
||||
### 3.1 为什么会冲突?
|
||||
|
||||
**一个端口同一时刻只能被一个程序监听。** 这就像一个房间只能住一户人家。
|
||||
|
||||
如果你尝试启动第二个服务在同一个端口上,就会看到这个经典错误:
|
||||
|
||||
```
|
||||
Error: listen EADDRINUSE :::3000
|
||||
```
|
||||
|
||||
翻译成人话就是:**"3000 号房已经有人住了,你进不去!"**
|
||||
|
||||
常见的冲突场景:
|
||||
- 上次的开发服务器没关干净,还在后台运行
|
||||
- 两个不同的项目用了相同的默认端口
|
||||
- 某个系统服务已经占用了你想要的端口
|
||||
|
||||
👇 **动手点点看**:
|
||||
试着在下面的模拟器里多次启动服务。当端口冲突时,对比"直接启动"和"智能启动"的不同处理方式。
|
||||
|
||||
<PortConflictDemo />
|
||||
|
||||
### 3.2 排查与解决
|
||||
|
||||
遇到端口冲突时,排查流程非常固定:
|
||||
|
||||
**macOS / Linux:**
|
||||
```bash
|
||||
# 第一步:查看谁在占用 3000 端口
|
||||
lsof -i :3000
|
||||
|
||||
# 第二步:拿到 PID 后,强制终止
|
||||
kill -9 <PID>
|
||||
```
|
||||
|
||||
**Windows:**
|
||||
```bash
|
||||
# 第一步:查看谁在占用 3000 端口
|
||||
netstat -ano | findstr :3000
|
||||
|
||||
# 第二步:终止进程
|
||||
taskkill /PID <PID> /F
|
||||
```
|
||||
|
||||
> 很多现代框架(Vite、Create React App 等)遇到端口冲突时会自动询问"是否换一个端口?"。但了解底层原理,能帮你更快地排查那些框架帮不了你的疑难杂症。
|
||||
|
||||
---
|
||||
|
||||
## 4. 开发中的"同源策略"与跨域
|
||||
|
||||
### 4.1 什么是"源"?
|
||||
|
||||
浏览器有一个安全机制叫做**同源策略(Same-Origin Policy)**:只有**协议、域名、端口**三者完全一致,才算"同源"。
|
||||
|
||||
| 地址 A | 地址 B | 是否同源 | 原因 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `http://localhost:5173` | `http://localhost:5173/about` | ✅ 同源 | 协议、域名、端口都一样 |
|
||||
| `http://localhost:5173` | `http://localhost:3000` | ❌ 不同源 | **端口不同**(5173 vs 3000) |
|
||||
| `http://localhost:5173` | `https://localhost:5173` | ❌ 不同源 | **协议不同**(http vs https) |
|
||||
|
||||
### 4.2 为什么前后端分离必然遇到跨域?
|
||||
|
||||
当你的项目架构是:
|
||||
|
||||
```
|
||||
前端 (Vite) → http://localhost:5173
|
||||
后端 (Express) → http://localhost:3000
|
||||
```
|
||||
|
||||
前端页面从 `:5173` 加载,然后用 `fetch('/api/users')` 去请求 `:3000` 的接口——**端口不一样,触发跨域限制!**
|
||||
|
||||
**两种常见解决方案:**
|
||||
|
||||
**方案一:后端配置 CORS**
|
||||
```javascript
|
||||
// Express 后端
|
||||
app.use(cors({ origin: 'http://localhost:5173' }))
|
||||
```
|
||||
|
||||
**方案二:前端配置代理(推荐)**
|
||||
```javascript
|
||||
// vite.config.js
|
||||
export default {
|
||||
server: {
|
||||
proxy: {
|
||||
'/api': 'http://localhost:3000'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
代理的原理:让 Vite 开发服务器帮你"转发"请求。浏览器以为自己在和 `:5173` 通信(同源),实际上 Vite 在背后偷偷帮你把请求转给了 `:3000`。
|
||||
|
||||
---
|
||||
|
||||
## 5. 实战排查:三个最常见的问题
|
||||
|
||||
👇 **动手点点看**:
|
||||
选择一个你遇到过的问题,跟着步骤一起排查。每一步都可以点击"执行"查看输出。
|
||||
|
||||
<PortTroubleshootDemo />
|
||||
|
||||
---
|
||||
|
||||
## 6. 名词对照表
|
||||
|
||||
| 英文术语 | 中文对照 | 解释 |
|
||||
| :--- | :--- | :--- |
|
||||
| **Port** | 端口 | 一个 0–65535 的数字,用来区分同一台机器上的不同网络服务。每个服务"监听"一个端口,等待客户端连接。 |
|
||||
| **localhost** | 本地主机 | 一个特殊域名,永远指向本机(127.0.0.1)。用于在不联网的情况下访问本机上运行的服务。 |
|
||||
| **Loopback Interface** | 环回接口 | 操作系统的虚拟网络接口。发往 127.0.0.1 的数据包不会离开本机,而是通过该接口"折返"回来。 |
|
||||
| **EADDRINUSE** | 地址已被使用 | Node.js / 操作系统报的错误,表示你要监听的端口已经被另一个程序占用了。 |
|
||||
| **CORS** | 跨域资源共享 | 浏览器安全机制。当前端页面尝试请求不同源(协议/域名/端口不同)的接口时,需要后端明确许可。 |
|
||||
| **Same-Origin Policy** | 同源策略 | 浏览器的安全基石:只允许同协议、同域名、同端口的请求自由通信,阻止跨域的数据读取。 |
|
||||
| **Proxy** | 代理 | 在开发环境中,代理服务器代替浏览器向后端转发请求,绕过浏览器的同源限制。 |
|
||||
| **0.0.0.0** | 所有接口 | 当服务监听 0.0.0.0 时,表示它接受来自任何网络接口(本机、局域网等)的连接。 |
|
||||
| **Well-known Ports** | 知名端口 | 0–1023 端口的统称,预留给 HTTP (80)、HTTPS (443)、SSH (22) 等标准协议。 |
|
||||
| **PID** | 进程 ID | 操作系统为每个运行中的程序分配的唯一编号,用于管理和终止进程。 |
|
||||
| **lsof** | 列出打开的文件 | macOS/Linux 命令,用于查看哪个进程占用了某个端口(`lsof -i :端口号`)。 |
|
||||
| **HMR** | 热模块替换 | 开发服务器的功能:你修改代码后,浏览器自动更新,无需手动刷新页面。底层通过 WebSocket 通知浏览器。 |
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
端口和 localhost 是开发环境中最基础、最高频的概念:
|
||||
|
||||
- **端口** = 一台机器上区分不同服务的"门牌号"(0–65535)
|
||||
- **localhost** = "自己找自己"的特殊地址(127.0.0.1),数据不出本机
|
||||
- **端口冲突**的本质是"一个门牌只能挂一块牌子"
|
||||
- **跨域**的本质是"端口不同 = 不同源",需要 CORS 或代理来解决
|
||||
|
||||
记住这四句话,你在开发环境里遇到的大多数网络问题,都能快速定位原因。
|
||||
|
||||
@@ -1,3 +1,178 @@
|
||||
# 正则表达式
|
||||
|
||||
> 待实现
|
||||
> 💡 **学习指南**:正则表达式看起来像天书?其实它只是一种"描述文本模式"的迷你语言。本章带你从零开始理解正则的核心思想,学会用几个关键符号解决 80% 的文本搜索和验证问题。
|
||||
|
||||
---
|
||||
|
||||
## 0. 你为什么需要正则表达式?
|
||||
|
||||
想象以下场景:
|
||||
- 从一大段日志里找出所有 IP 地址
|
||||
- 验证用户输入的邮箱格式是否合法
|
||||
- 把文本中所有的日期格式从 `2024/01/15` 替换为 `2024-01-15`
|
||||
- 从网页源码中提取所有链接
|
||||
|
||||
**用普通字符串搜索?** 你需要写一大堆 `if-else` 判断逻辑。
|
||||
**用正则表达式?** 一行模式搞定。
|
||||
|
||||
---
|
||||
|
||||
## 1. 正则入门:三分钟上手
|
||||
|
||||
👇 动手点点看:输入正则表达式,实时查看匹配结果
|
||||
|
||||
<RegexDemo />
|
||||
|
||||
::: tip 💡 一句话理解
|
||||
正则表达式 = **用特殊符号描述"你想找什么样的文本"**。`\d` 代表数字,`+` 代表一个或多个,所以 `\d+` 就是"一个或多个数字"。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 2. 核心概念:像搭积木一样组合
|
||||
|
||||
正则的本质是用**三类积木**搭出你想要的模式:
|
||||
|
||||
### 2.1 积木一:字符类(匹配什么字符)
|
||||
|
||||
| 语法 | 含义 | 示例 |
|
||||
|---|---|---|
|
||||
| `.` | 任意字符 | `a.c` → abc, a1c, a c |
|
||||
| `\d` | 数字 [0-9] | `\d\d` → 42, 99 |
|
||||
| `\w` | 字母/数字/下划线 | `\w+` → hello, user_1 |
|
||||
| `\s` | 空白字符 | 匹配空格、Tab |
|
||||
| `[abc]` | 集合中的任意一个 | `[aeiou]` → 元音字母 |
|
||||
| `[^abc]` | 不在集合中的 | `[^0-9]` → 非数字字符 |
|
||||
|
||||
### 2.2 积木二:量词(匹配几次)
|
||||
|
||||
| 语法 | 含义 | 示例 |
|
||||
|---|---|---|
|
||||
| `*` | 0 次或多次 | `ab*` → a, ab, abbb |
|
||||
| `+` | 1 次或多次 | `ab+` → ab, abbb(不匹配 a) |
|
||||
| `?` | 0 次或 1 次 | `colou?r` → color, colour |
|
||||
| `{3}` | 恰好 3 次 | `\d{3}` → 123 |
|
||||
| `{2,4}` | 2 到 4 次 | `\d{2,4}` → 12, 1234 |
|
||||
|
||||
### 2.3 积木三:位置和分组
|
||||
|
||||
| 语法 | 含义 | 示例 |
|
||||
|---|---|---|
|
||||
| `^` | 行首 | `^Hello` → 以 Hello 开头的行 |
|
||||
| `$` | 行尾 | `end$` → 以 end 结尾的行 |
|
||||
| `\b` | 单词边界 | `\bcat\b` → cat(不匹配 catch) |
|
||||
| `(...)` | 捕获分组 | `(\d+)-(\d+)` → 分别捕获 |
|
||||
| `a\|b` | 或 | `cat\|dog` → cat 或 dog |
|
||||
|
||||
---
|
||||
|
||||
## 3. 实战:常见验证模式
|
||||
|
||||
### 3.1 邮箱验证
|
||||
|
||||
```
|
||||
[\w.+-]+@[\w-]+\.[\w.]+
|
||||
```
|
||||
|
||||
拆解:
|
||||
- `[\w.+-]+` — 用户名部分(字母数字点加号横杠)
|
||||
- `@` — 字面量 @
|
||||
- `[\w-]+` — 域名部分
|
||||
- `\.` — 转义的点
|
||||
- `[\w.]+` — 顶级域名
|
||||
|
||||
### 3.2 手机号验证(中国)
|
||||
|
||||
```
|
||||
1[3-9]\d{9}
|
||||
```
|
||||
|
||||
拆解:
|
||||
- `1` — 以 1 开头
|
||||
- `[3-9]` — 第二位是 3-9
|
||||
- `\d{9}` — 后面跟 9 位数字
|
||||
|
||||
### 3.3 密码强度检查
|
||||
|
||||
```
|
||||
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$
|
||||
```
|
||||
|
||||
拆解:
|
||||
- `(?=.*[a-z])` — 至少一个小写字母(前瞻断言)
|
||||
- `(?=.*[A-Z])` — 至少一个大写字母
|
||||
- `(?=.*\d)` — 至少一个数字
|
||||
- `.{8,}` — 总长度至少 8 位
|
||||
|
||||
---
|
||||
|
||||
## 4. 在代码中使用正则
|
||||
|
||||
### JavaScript
|
||||
|
||||
```javascript
|
||||
const text = '联系方式:13812345678 或 15099887766'
|
||||
const regex = /1[3-9]\d{9}/g
|
||||
const phones = text.match(regex)
|
||||
// ['13812345678', '15099887766']
|
||||
|
||||
// 替换
|
||||
text.replace(/\d{4}(?=\d{4}$)/, '****')
|
||||
// 隐藏手机号中间四位
|
||||
|
||||
// 验证
|
||||
/^[\w.+-]+@[\w-]+\.[\w.]+$/.test('user@example.com')
|
||||
// true
|
||||
```
|
||||
|
||||
### Python
|
||||
|
||||
```python
|
||||
import re
|
||||
|
||||
text = '价格是 99 元,优惠 20 元'
|
||||
numbers = re.findall(r'\d+', text)
|
||||
# ['99', '20']
|
||||
|
||||
# 替换
|
||||
re.sub(r'\d+', 'X', text)
|
||||
# '价格是 X 元,优惠 X 元'
|
||||
|
||||
# 分组捕获
|
||||
match = re.search(r'(\d+)-(\d+)', '2024-01-15')
|
||||
match.group(1) # '2024'
|
||||
match.group(2) # '01'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 贪婪 vs 懒惰:一个关键区别
|
||||
|
||||
```
|
||||
文本: <b>hello</b> and <b>world</b>
|
||||
```
|
||||
|
||||
| 模式 | 匹配结果 | 说明 |
|
||||
|---|---|---|
|
||||
| `<b>.*</b>` | `<b>hello</b> and <b>world</b>` | 贪婪:尽量多匹配 |
|
||||
| `<b>.*?</b>` | `<b>hello</b>` | 懒惰:尽量少匹配 |
|
||||
|
||||
::: tip 💡 记住
|
||||
默认是贪婪模式。在量词后面加 `?` 变成懒惰模式。大多数时候,你需要的是懒惰模式。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 6. 总结
|
||||
|
||||
::: tip 📚 核心要点
|
||||
1. **正则 = 描述文本模式的迷你语言**,用于搜索、匹配、替换
|
||||
2. **三类积木**:字符类(匹配什么)+ 量词(匹配几次)+ 位置/分组
|
||||
3. **\d \w \s** 是最常用的三个字符类,覆盖数字、单词、空白
|
||||
4. **不需要从零写**:常见场景都有成熟的正则模式可以复用
|
||||
5. **贪婪 vs 懒惰**:默认贪婪(多匹配),加 `?` 变懒惰(少匹配)
|
||||
:::
|
||||
|
||||
**下一步学习**:
|
||||
- [环境变量与 PATH](./environment-path) - 理解系统配置
|
||||
- [SSH 与密钥认证](./ssh-authentication) - 安全连接远程服务器
|
||||
|
||||
@@ -1,3 +1,138 @@
|
||||
# SSH 与密钥认证
|
||||
|
||||
> 待实现
|
||||
> 💡 **学习指南**:每次 `git push` 输密码?连服务器总被提示"Permission denied"?本章用 5 分钟带你搞懂 SSH 密钥认证的原理,以及如何一键免密登录 GitHub 和服务器。
|
||||
|
||||
---
|
||||
|
||||
## 0. 你一定遇到过这些场景
|
||||
|
||||
- `git push` 时反复弹出密码输入框,烦不胜烦
|
||||
- SSH 连接服务器失败,不知道 `id_rsa` 和 `id_ed25519` 是什么
|
||||
- 听说"公钥"和"私钥",但搞不清哪个给别人、哪个自己留
|
||||
|
||||
**核心矛盾**:密码不安全、又麻烦。SSH 密钥就是用来同时解决安全性和便利性的方案。
|
||||
|
||||
---
|
||||
|
||||
## 1. 密码 vs 密钥:为什么密钥更好?
|
||||
|
||||
👇 动手点点看:对比密码登录和密钥登录的区别
|
||||
|
||||
<SSHAuthDemo />
|
||||
|
||||
::: tip 💡 一句话总结
|
||||
密码登录 = 每次把密码发过去让对方核对(密码可能被截获);
|
||||
密钥登录 = 证明"我有钥匙"但不用把钥匙给你看(私钥永不传输)。
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 2. 非对称加密:公钥和私钥
|
||||
|
||||
SSH 密钥基于**非对称加密**,一次生成两把钥匙:
|
||||
|
||||
| | 私钥 (Private Key) | 公钥 (Public Key) |
|
||||
|---|---|---|
|
||||
| **保存位置** | 你的电脑 `~/.ssh/id_ed25519` | 服务器/GitHub |
|
||||
| **可以给别人吗** | ❌ 绝不 | ✅ 随便给 |
|
||||
| **功能** | 签名(证明身份) | 验签(验证身份) |
|
||||
| **类比** | 钥匙 | 锁 |
|
||||
|
||||
### 常见密钥类型
|
||||
|
||||
| 类型 | 命令 | 推荐度 | 说明 |
|
||||
|---|---|---|---|
|
||||
| **Ed25519** | `ssh-keygen -t ed25519` | ⭐⭐⭐ | 最新最快最安全 |
|
||||
| **RSA** | `ssh-keygen -t rsa -b 4096` | ⭐⭐ | 兼容性好,但较慢 |
|
||||
| **ECDSA** | `ssh-keygen -t ecdsa` | ⭐ | 一般不推荐 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 实战:生成并配置 SSH 密钥
|
||||
|
||||
### 3.1 生成密钥对
|
||||
|
||||
```bash
|
||||
ssh-keygen -t ed25519 -C "your@email.com"
|
||||
```
|
||||
|
||||
执行后会提示:
|
||||
- **文件路径**:直接回车用默认路径 `~/.ssh/id_ed25519`
|
||||
- **密码短语**:可以设置额外保护(也可留空)
|
||||
|
||||
### 3.2 把公钥添加到 GitHub
|
||||
|
||||
```bash
|
||||
# 1. 复制公钥内容
|
||||
cat ~/.ssh/id_ed25519.pub | pbcopy # macOS
|
||||
cat ~/.ssh/id_ed25519.pub | xclip # Linux
|
||||
|
||||
# 2. 打开 GitHub → Settings → SSH and GPG keys → New SSH key
|
||||
# 3. 粘贴公钥,保存
|
||||
|
||||
# 4. 测试连接
|
||||
ssh -T git@github.com
|
||||
# 成功会看到: Hi username! You've been authenticated...
|
||||
```
|
||||
|
||||
### 3.3 把公钥添加到服务器
|
||||
|
||||
```bash
|
||||
# 方式一:ssh-copy-id(推荐)
|
||||
ssh-copy-id user@your-server
|
||||
|
||||
# 方式二:手动复制
|
||||
cat ~/.ssh/id_ed25519.pub | ssh user@server "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. SSH Config:告别长命令
|
||||
|
||||
在 `~/.ssh/config` 中配置别名,一次配置终身受益:
|
||||
|
||||
```
|
||||
Host dev
|
||||
HostName 192.168.1.100
|
||||
User deploy
|
||||
IdentityFile ~/.ssh/id_ed25519
|
||||
|
||||
Host github.com
|
||||
HostName github.com
|
||||
User git
|
||||
IdentityFile ~/.ssh/id_ed25519
|
||||
```
|
||||
|
||||
配置后的效果:
|
||||
|
||||
| 之前 | 之后 |
|
||||
|---|---|
|
||||
| `ssh -i ~/.ssh/id_ed25519 deploy@192.168.1.100` | `ssh dev` |
|
||||
| 每次都要记 IP 和用户名 | 记一个别名就够 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 常见问题排查
|
||||
|
||||
| 问题 | 原因 | 解决方案 |
|
||||
|---|---|---|
|
||||
| `Permission denied (publickey)` | 公钥没添加到服务器 | `ssh-copy-id user@server` |
|
||||
| `WARNING: UNPROTECTED PRIVATE KEY FILE` | 私钥文件权限太宽 | `chmod 600 ~/.ssh/id_ed25519` |
|
||||
| `Could not resolve hostname` | SSH Config 配置有误 | 检查 `~/.ssh/config` 格式 |
|
||||
| GitHub 还是要密码 | 用的 HTTPS 而非 SSH | 改用 `git@github.com:user/repo.git` |
|
||||
|
||||
---
|
||||
|
||||
## 6. 总结
|
||||
|
||||
::: tip 📚 核心要点
|
||||
1. **密钥 > 密码**:私钥永不传输,比密码安全得多
|
||||
2. **推荐 Ed25519**:最现代的密钥算法,速度快、安全性高
|
||||
3. **公钥随便给,私钥绝不泄露**:记住这条铁律
|
||||
4. **SSH Config**:配一次别名,之后 `ssh 别名` 一键连接
|
||||
5. **GitHub/GitLab**:添加公钥后,`git push/pull` 再也不需要输密码
|
||||
:::
|
||||
|
||||
**下一步学习**:
|
||||
- [端口与 localhost](./ports-localhost) - 理解网络连接的基础
|
||||
- [环境变量与 PATH](./environment-path) - 理解系统配置
|
||||
|
||||
Reference in New Issue
Block a user