# 阿里云 + 宝塔面板 · 完整部署指南

> 把"薪火 Kindling"网站从纯静态升级为带实时 AI 能力的站点。
> 这份手册按你点鼠标的顺序写，照着做大概 **2~3 小时** 完成。

---

## 总架构

```
浏览器（学生）
    ↓ HTTPS  https://kindling.top
[阿里云 ECS] —— 宝塔面板管控
    │
    ├── Nginx（80/443）
    │     ├── /                → 静态网站（kindling-low-level / mid / high...）
    │     └── /api/            → 反向代理 → 127.0.0.1:3000
    │
    └── PM2 守护
          └── Node.js (server.js · 端口 3000)
                ├─→ DeepSeek API     （对话、评分）
                ├─→ 阿里云百炼 API    （Qwen-VL、通义万相、Embedding）
                └─→ 硅基流动 API      （备用聚合：Qwen / GLM / Llama）
```

**关键好处**：
- 前端代码里没有任何 API key，学生看不到，盗刷不了
- 所有调用都在你这台 ECS 上中转，配额由你控制
- 同域名（同源）部署，没有跨域、不需要复杂 CORS 配置

---

## 第 0 步 · 你需要准备什么

| 项 | 哪里搞 | 大概费用 |
|---|---|---|
| 阿里云 ECS（2核2G足够起步） | https://ecs.console.aliyun.com | 起步约 ¥80~200 / 月 |
| 域名（kindling.top 或别的） | https://wanwang.aliyun.com | ¥30~100 / 年 |
| **域名 ICP 备案**（**必须**，国内服务器没备案打不开） | 阿里云控制台 → 备案 | 免费，约 7~20 天审核 |
| 宝塔 Linux 面板 | https://www.bt.cn （ECS 上一行命令安装） | 免费版够用 |
| DeepSeek 账号 + ¥50 充值 | https://platform.deepseek.com | ¥50 起 |
| 阿里云百炼账号 | https://bailian.console.aliyun.com | 注册送配额 |
| 硅基流动账号（备用） | https://siliconflow.cn | 注册送 ¥14 |

> **没备案不能上线。** 如果你买的是阿里云"国际版"或"香港地区"的 ECS，可以不备案，但访问速度会慢一些。

---

## 第 1 步 · 装宝塔面板（已装请跳过）

SSH 登进 ECS（用阿里云控制台的 Workbench 或 ssh 工具），执行：

```bash
# Ubuntu / Debian
wget -O install.sh https://download.bt.cn/install/install-ubuntu_6.0.sh && sudo bash install.sh ed8484bec

# CentOS
yum install -y wget && wget -O install.sh https://download.bt.cn/install/install_6.0.sh && sh install.sh ed8484bec
```

装完会显示一段：
```
外网面板地址: https://你的IP:8888/xxxxx
账号: xxxxx
密码: xxxxx
```

把这三行存好。回阿里云**安全组**里把 8888 端口加白名单（仅自己 IP），然后浏览器打开面板地址登进去。

> 进入宝塔后，它会让你装"LNMP"全家桶。你可以**只勾选 Nginx**，PHP 和 MySQL 这次用不上。

---

## 第 2 步 · 在宝塔里装 Node.js

宝塔面板左侧 → **软件商店** → 搜索 **"Node.js 版本管理器"** → 安装。

装完后：

1. 软件商店 → 已安装 → Node.js 版本管理器 → **设置**
2. **安装 Node.js 18 或 20**（建议 20 LTS）
3. 设置当前版本为 20
4. 同一个面板里点 **"PM2 管理器"** → 安装

> 这时 SSH 终端运行 `node -v` 应当能看到版本号 v20.x.x。

---

## 第 3 步 · 上传网站文件 + 创建站点

### 3.1 在宝塔创建站点

宝塔左侧 → **网站** → 添加站点：

- 域名：`kindling.top`（你自己的）
- 根目录：`/www/wwwroot/kindling.top`（默认就行）
- PHP 版本：**纯静态**
- 数据库：不创建

### 3.2 上传网站文件

把整个 `final website in 中文` 文件夹（也就是 `index.html`、`kindling-low-level/`、`kindling-middle-level/`、`kindling-high_level/`、`kindling-iteration-2/` 这一层）上传到 `/www/wwwroot/kindling.top/` 下。

可以用：
- 宝塔自带的"文件"管理器（直接拖拽上传 zip 然后右键解压）
- 或 SSH 用 `scp` / `rsync`
- 或 Git pull（把代码先推到 GitLab/Gitee/CodeUp）

上传完，在浏览器里访问 `http://你的域名/` 应当能看到首页。如果显示 403，去站点设置里把"运行目录"改成 `/`。

### 3.3 申请 SSL 证书

站点设置 → SSL → **Let's Encrypt** → 勾选域名 → **申请**。
申请成功后开启 **强制 HTTPS**。

至此，你已经有一个可用的 HTTPS 静态站点。

---

## 第 4 步 · 部署 AI 后端服务

### 4.1 上传 server 代码

把整个 `kindling-iteration-2/server/` 文件夹复制到 ECS 上一个**和站点目录分开**的地方，比如：

```
/www/wwwroot/kindling-api/
```

> 不要放在 `/www/wwwroot/kindling.top/` 下面，否则源代码会被静态网站直接暴露出去。

可以这样从仓库里拷过去：
```bash
sudo mkdir -p /www/wwwroot/kindling-api
sudo cp -r /www/wwwroot/kindling.top/kindling-iteration-2/server/* /www/wwwroot/kindling-api/
sudo chown -R www:www /www/wwwroot/kindling-api
```

### 4.2 创建并填写 .env

```bash
cd /www/wwwroot/kindling-api
cp .env.example .env
nano .env   # 或在宝塔文件管理器里直接编辑
```

填上你的三把 key：

```env
PORT=3000
ALLOWED_ORIGIN=https://kindling.top,https://www.kindling.top
RATE_LIMIT_PER_MIN=30

DEEPSEEK_API_KEY=sk-真实的 key
DASHSCOPE_API_KEY=sk-真实的 key
SILICONFLOW_API_KEY=sk-真实的 key
```

> 三把 key 怎么拿：
> - **DeepSeek**：https://platform.deepseek.com → 充值 ¥50 → API Keys → 创建
> - **百炼（DashScope）**：https://bailian.console.aliyun.com → API-KEY 管理 → 创建
> - **硅基流动**：https://siliconflow.cn → 注册 → 控制台 → API 密钥 → 新建

### 4.3 安装依赖

宝塔面板 → 终端，或 SSH：

```bash
cd /www/wwwroot/kindling-api
npm install --production
```

依赖装完会有个 `node_modules/` 目录。

### 4.4 用 PM2 启动

```bash
cd /www/wwwroot/kindling-api
pm2 start ecosystem.config.js
pm2 save              # 保存当前进程列表
pm2 startup           # 让 PM2 跟随系统开机自启（按提示再运行它打印的命令）
```

也可以在宝塔的 **PM2 管理器** UI 里：
- 添加项目 → 项目目录 `/www/wwwroot/kindling-api` → 启动文件 `server.js` → 启动

确认运行：

```bash
pm2 status
curl http://127.0.0.1:3000/api/health
```

应当返回 `{ "ok": true, "providers": { "deepseek": true, ... } }`。

---

## 第 5 步 · Nginx 反向代理 /api/ → :3000

这一步是把"前端走 HTTPS、后端走 3000"无缝拼起来的关键。

### 5.1 找到站点的 Nginx 配置

宝塔 → 网站 → kindling.top → **配置文件** → 翻到 `server { ... }` 块。

### 5.2 在 server 块里加一段 location

把下面这段贴到 `server { ... }` 里，**放在已有的 `location /` 之前**（顺序很重要，先匹配 /api 才不会被静态文件兜底吃掉）：

```nginx
# AI 后端反代
location /api/ {
    proxy_pass http://127.0.0.1:3000/api/;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    # 流式（SSE）必备
    proxy_buffering off;
    proxy_cache off;
    proxy_read_timeout 120s;
    proxy_send_timeout 120s;

    # 大点的图片 base64 上行用
    client_max_body_size 10m;
}
```

> 如果你打算 *只* 在 https 下提供 AI 服务，就放进 443 的 server 块里（宝塔默认只有一个 server 既监听 80 又监听 443，一份就够）。

### 5.3 保存 → 测试 → 重载

宝塔会自动测试配置；保存后它会自己 `nginx -t && nginx -s reload`。
如果它报错，常见原因：
- 漏分号
- proxy_pass 末尾的 `/` 漏了或多了
- location 块写错了位置

### 5.4 验证

浏览器打开：

```
https://kindling.top/api/health
```

应当返回 JSON：
```json
{ "ok": true, "providers": { "deepseek": true, "dashscope": true, ... } }
```

如果看到这个，**整个链路打通了**。剩下的都是前端怎么用。

---

## 第 6 步 · 在已有静态页里调用 AI

随便打开任何一个项目页，比如 `kindling-middle-level/projects/code-02-add-claude.html`，在 `</body>` 之前加一行：

```html
<script src="/kindling-iteration-2/kindling-ai-sdk.js"></script>
```

然后页面里任何地方都能用：

```html
<button id="ask">问 AI</button>
<div id="answer"></div>
<script>
document.getElementById('ask').onclick = async () => {
  const out = document.getElementById('answer');
  out.textContent = '想一想……';
  await kindlingAI.chat({
    system: '你是温和的中学编程小老师，用简单中文回答。',
    user: '为什么我的网页 button 点了没反应？',
    stream: true,
    onToken: (t) => { out.textContent += t; }
  });
};
</script>
```

> 注意 SDK 默认走**同域 `/api/v1/*`**，不需要 `data-endpoint`。
> 这正是为什么我们走 Nginx 反代——前端的 `fetch('/api/v1/chat')` 实际上落到了 Nginx → Node 上，但浏览器看来就是同源调用，没有 CORS、没有跨域、没有泄密。

---

## 第 7 步 · 给宝塔加几条防护（可选但推荐）

### 7.1 防火墙：只开必要端口

宝塔 → 安全 → 防火墙：
- 22（SSH）：限制为你的 IP
- 80, 443（网站）：放开
- 8888（宝塔后台）：限制为你的 IP
- 3000（Node）：**不要开**！它只该听 127.0.0.1

server.js 里写的就是 `app.listen(PORT, '127.0.0.1', ...)`，**别人从外网直接打 3000 端口打不进来**——这是设计的一部分，必须经过 Nginx。

### 7.2 给宝塔后台改个奇怪的入口路径

宝塔 → 面板设置 → 安全入口：把 `/btxxxxx` 改成你自己记得的怪路径。

### 7.3 一个简单的"日花费监控"

DeepSeek / 百炼 / 硅基流动后台都有"用量"页面。建议：
- 每个平台**单独充值小额**（比如 DeepSeek ¥100、百炼 ¥50、硅基流动 ¥50）
- 在 server.js 里 `RATE_LIMIT_PER_MIN=30` 已是基础保护
- 如果想再细致，可以按 IP 计天数（这一版没写，需要可加）

---

## 第 8 步 · 改完代码以后怎么发版

### 8.1 改前端（HTML/CSS/JS）

宝塔文件管理器直接编辑 → 保存。
浏览器 Ctrl+F5 强刷就能看到。

### 8.2 改后端（server.js）

```bash
cd /www/wwwroot/kindling-api
# 改完代码后
pm2 reload kindling-ai
```

`reload` 是无缝重启（旧请求处理完才退出），用户感觉不到中断。

### 8.3 看日志

```bash
pm2 logs kindling-ai          # 实时
pm2 logs kindling-ai --lines 200   # 最近 200 行
```

或者宝塔 PM2 管理器里有图形化日志查看。

---

## 第 9 步 · 备份

宝塔 → 计划任务 → 添加：
- **网站备份**：每天凌晨备份 `/www/wwwroot/kindling.top` 到宝塔自己的 backup 目录
- **Node 服务备份**：每天备份 `/www/wwwroot/kindling-api`（但**别**备份 `node_modules`，太大没必要）

更稳妥：阿里云 OSS 跨地域同步，防止整台 ECS 挂了。

---

## 常见问题速查

| 现象 | 一般原因 | 怎么修 |
|---|---|---|
| `/api/health` 502 | Node 没起，或端口不对 | `pm2 status` 看进程；`curl 127.0.0.1:3000/api/health` 看本地 |
| `/api/health` 404 | Nginx 没生效，或 location 顺序不对 | 重载 Nginx；把 `location /api/` 放到 `location /` 上面 |
| 调用 chat 返回 401 | DeepSeek key 错了或过期 | 重新去后台拿一把，改 `.env`，`pm2 reload` |
| 流式（streaming）一卡一卡 | Nginx 在缓冲 SSE | 确认 `proxy_buffering off;` 这行有 |
| 学生看不见 AI 回复 | 大概率没刷新 / 没引 sdk | 控制台看 Network，看 `/api/v1/chat` 有没有 200 |
| 突然花了一笔大钱 | 可能被薅羊毛 | 把 `RATE_LIMIT_PER_MIN` 调到 10；查 Nginx access.log 哪个 IP 在刷 |

---

## 附：备案前如果想先测试

如果备案还在审核中，又想看看效果，三个临时方案：

1. **改用阿里云"中国香港"或"新加坡"地域**的 ECS —— 不需要备案，但延迟略高
2. 临时用域名直接挂在阿里云 OSS（静态站点）+ 一个香港 ECS 跑 Node —— 后端用一级域名 `https://api.xxx.com`
3. 用宝塔分配的临时域名，但只能你自己测，无法对外正式上线

正式上线还是建议把备案做完。

---

## 检查清单（部署完照着过一遍）

- [ ] 宝塔面板可以登录
- [ ] 站点 https://kindling.top 能打开（绿锁）
- [ ] 静态资源（图片、CSS）正常加载
- [ ] `/api/health` 返回 `ok: true`
- [ ] 三个 provider 都是 `true`（key 都填了）
- [ ] 一个项目页里 `kindlingAI.chat({...})` 能拿到回复
- [ ] PM2 列表里 `kindling-ai` 状态 `online`
- [ ] `pm2 startup` + `pm2 save` 已执行（重启 ECS 不会掉）
- [ ] 防火墙关掉了 3000 端口对外
- [ ] 三个 AI 平台都有充值，且**没**勾选自动续费太大额度
- [ ] 网站备份计划已开

完成 = 上线。

— 薪火 Kindling 工程化部署文档 v1（中国版）
