# KOC LOOP Sites 旧版部署指南

> 此文档仅适用于历史 `codex/sites-release-controls` 分支。`main` 已切换为 Next.js + MySQL + Nginx 私有化部署，正式部署请使用 [KOC LOOP 私有化部署指南](KOC%20LOOP%20私有化部署指南.md)，不要按本文把 `main` 发布到 Sites。

KOC LOOP 由两个独立站点组成：

| 站点 | 代码目录 | 作用 | 生产地址 |
| --- | --- | --- | --- |
| 运营后台 | 仓库根目录 | 任务导入、内容分发、KOC 资源、数据回收和自动采集 | [KOC LOOP 后台](https://koc-loop-mvp-wufp.pyeongwu.chatgpt.site) |
| KOC 领取站点 | `koc-portal/` | 外部 KOC 领取笔记、查看内容、回填发布及第 7 天数据 | [KOC 领取站点](https://koc-task-portal-wufp.pyeongwu.chatgpt.site) |

两个站点都部署在 Sites。运营后台绑定 Cloudflare D1 数据库和 R2 文件存储；KOC 领取站点不保存业务数据，通过公开的合作方接口访问后台。

## 1. 准备项目

运行环境：

- Node.js `>= 22.13.0`
- npm
- Git
- 可访问 Sites 项目和生产环境变量的账号

下载代码并安装两个站点的依赖：

```bash
git clone ssh://git@gta.gbotai.cn:42001/wufengping/koc-loop.git

cd koc-loop
npm ci

cd koc-portal
npm ci
```

## 2. 配置运营后台

### 2.1 资源绑定

运营后台的 `.openai/hosting.json` 必须保留以下逻辑绑定：

```json
{
  "project_id": "以仓库现有配置为准",
  "d1": "DB",
  "r2": "UPLOADS"
}
```

- `DB`：保存任务、笔记、领取、发布、账号资源和采集记录。
- `UPLOADS`：保存飞书配图、发布截图和创作者中心截图。
- 已有 `project_id` 时必须复用，不能重新创建站点，否则会产生新的数据库、存储和生产地址。

KOC 领取站点使用 `koc-portal/.openai/hosting.json` 中的既有 `project_id`，不绑定 D1 和 R2。

### 2.2 生产环境变量

在运营后台 Sites 项目的运行时环境变量中配置以下内容，不能保留示例占位值：

| 变量 | 类型 | 用途 |
| --- | --- | --- |
| `SUPER_ADMIN_USERNAME` | 密钥 | 首次初始化时创建唯一超级管理员的登录账号 |
| `SUPER_ADMIN_PASSWORD` | 密钥 | 首次初始化时创建唯一超级管理员的初始密码 |
| `ADMIN_INTERNAL_TOKEN` | 密钥 | 定时采集补跑、内部管理接口调用鉴权 |
| `KOC_MCP_API_KEY` | 密钥 | Agent 调用 `/api/mcp` 的独立 Bearer 密钥 |
| `KOC_PORTAL_URL` | 普通变量 | KOC 领取站点生产 Origin，用于生成领取链接和 CORS 校验 |
| `AI_TOOL_CENTER_MCP_URL` | 普通变量 | 正式数据采集 MCP 地址 |
| `AI_TOOL_CENTER_MCP_KEY` | 密钥 | 正式数据采集 MCP 密钥 |
| `FEISHU_APP_ID` | 密钥 | 飞书自建应用 App ID |
| `FEISHU_APP_SECRET` | 密钥 | 飞书自建应用 App Secret |

注意：

- 密钥只能保存在本地 `.dev.vars` 或 Sites 运行时环境变量中，禁止写入 Git、部署文档、命令历史或日志。
- `KOC_MCP_API_KEY` 必须和 `ADMIN_INTERNAL_TOKEN`、`AI_TOOL_CENTER_MCP_KEY` 使用三份不同的随机值，不能复用。
- `SUPER_ADMIN_USERNAME` 和 `SUPER_ADMIN_PASSWORD` 只在系统尚无超级管理员时用于初始化。账号创建后，后续账号和密码调整统一在后台“用户管理”中完成。
- `KOC_PORTAL_URL` 应填写领取站点的完整 Origin，例如 `https://站点域名`，不要附加任务路径或查询参数。
- 领取站点当前不需要单独配置生产环境变量。它在 `koc-portal/app/page.tsx` 中指向运营后台生产地址；后台地址变化时必须同步修改并重新部署领取站点。

### 2.3 飞书应用权限

飞书自建应用至少需要：

- 读取电子表格；
- 读取知识库节点；
- 下载云文档素材。

同时需要将飞书应用添加到目标知识库或目标电子表格的文档应用中，否则即使 App ID 和 App Secret 正确也无法导入内容。

### 2.4 Agent MCP

生产 MCP 地址固定为：

```text
https://运营后台域名/api/mcp
```

请求使用独立 Bearer 鉴权：

```text
Authorization: Bearer <KOC_MCP_API_KEY>
```

当前应发现 13 个工具：

- 创建任务：`create_distribution_task`
- 任务查询：`task_list`、`task_get`
- 数据回收：`recovery_list`、`recovery_export`
- 数据采集：`collection_plan_set`、`collection_run_due`、`collection_collect_now`、`collection_retry_failed`
- KOC 资源：`resource_search`、`resource_get`、`resource_backfill_profile`、`resource_export`

`recovery_export` 和 `resource_export` 返回一次性、15 分钟有效的下载链接。链接只承载随机导出令牌，不包含 MCP 密钥；任务导出的原图和截图仍直接嵌入 Excel。

## 3. 本地开发

复制后台环境变量示例：

```bash
cd koc-loop
cp .dev.vars.example .dev.vars
```

在 `.dev.vars` 中填写本地测试配置。不要提交该文件。

先启动运营后台，使用 `3001` 端口：

```bash
cd koc-loop
npm run dev -- --port 3001
```

再启动 KOC 领取站点，使用 `3000` 端口：

```bash
cd koc-loop/koc-portal
npm run dev -- --port 3000
```

本地领取站点会自动请求 `http://localhost:3001` 的后台接口。

## 4. 发布前验证

运营后台：

```bash
cd koc-loop
npm test
npm run lint
```

KOC 领取站点：

```bash
cd koc-loop/koc-portal
npm test
npm run lint
```

全部命令成功后再提交代码：

```bash
git status
git add <本次修改的文件>
git commit -m "说明本次变更"
git push origin main
```

不要提交 `.dev.vars`、生产密钥、临时压缩包、构建缓存或本地数据库文件。

## 5. 首次部署

KOC LOOP 使用 Sites 版本发布流程，不需要安装 systemd 服务，也不需要自行创建 Cloudflare Worker、D1 或 R2。

### 5.1 部署运营后台

1. 以仓库根目录作为构建目录。
2. 执行 `npm run build`。
3. 确认生成：
   - `dist/server/index.js`
   - `dist/.openai/hosting.json`
   - `dist/.openai/drizzle/`
4. 将已经推送到 Git 的同一提交保存为 Sites 版本。
5. 公开部署该版本，并等待部署状态变为成功。
6. 在 Sites 中补齐第 2 节列出的生产环境变量。
7. 将站点访问方式设置为公开；后台仍会通过账号密码和角色权限做业务访问控制，MCP 则使用独立 Bearer 密钥。

### 5.2 部署 KOC 领取站点

1. 以 `koc-portal/` 作为构建目录。
2. 确认 `PRODUCTION_ADMIN_ORIGIN` 指向已部署的运营后台地址。
3. 执行 `npm run build`。
4. 复用 `koc-portal/.openai/hosting.json` 中的既有 Sites 项目。
5. 保存并公开部署新版本。
6. 将领取站点生产地址填入运营后台的 `KOC_PORTAL_URL`。
7. 如果 `KOC_PORTAL_URL` 是首次设置或发生变化，重新部署一次运营后台，使新环境变量进入生产版本。

## 6. 更新代码

常规更新流程：

```bash
cd koc-loop
git pull --ff-only
npm ci
npm test
npm run lint
```

根据修改范围决定部署对象：

- 只修改根目录的后台、接口、数据库或采集逻辑：部署运营后台。
- 只修改 `koc-portal/`：部署 KOC 领取站点。
- 同时修改接口和领取页面：先部署运营后台，再部署 KOC 领取站点。
- 修改共享链路、站点地址或 CORS：两个站点都要部署并完成联调。

每次部署都必须复用对应 `.openai/hosting.json` 中的 `project_id`，保存新版本后再发布，不能直接用未保存的本地构建覆盖生产环境。

## 7. 数据库变更

修改 `db/schema.ts` 后生成迁移：

```bash
cd koc-loop
npm run db:generate
```

提交前检查：

- `drizzle/` 中只新增预期迁移；
- 不允许手工修改已经在线执行过的旧迁移；
- `npm test` 通过；
- 新字段兼容已有数据和空值。

本版本新增 `mcp_export_tokens` 表，用于保存导出令牌哈希、导出类型、筛选范围和过期时间。数据库只保存令牌哈希，不保存可直接使用的明文令牌；过期记录会在后续签发时清理。

后台构建会把 `drizzle/` 自动复制到 `dist/.openai/drizzle/`，Sites 发布时随版本处理数据库迁移。重新部署不会清空 D1 或 R2 数据。

## 8. 每日自动采集

运营后台 Worker 配置了 Cloudflare Cron：

```text
0 1 * * *
```

Cloudflare Cron 使用 UTC，`01:00 UTC` 对应北京时间每天 `09:00`。定时任务会：

1. 确认数据库结构；
2. 执行当天已创建的笔记数据采集任务；
3. 回写点赞、收藏、评论、总互动和采集状态；
4. 尝试补全账号主页、小红书号、IP 地址和粉丝数。

手动补跑时先将密钥读入当前终端，不要直接写进命令：

```bash
export KOC_ADMIN_URL="https://koc-loop-mvp-wufp.pyeongwu.chatgpt.site"
read -s ADMIN_INTERNAL_TOKEN
export ADMIN_INTERNAL_TOKEN

curl -X POST "$KOC_ADMIN_URL/api/action" \
  -H "Content-Type: application/json" \
  -H "x-koc-admin-token: $ADMIN_INTERNAL_TOKEN" \
  -d '{"action":"run_due_collections"}'

unset ADMIN_INTERNAL_TOKEN
```

接口成功但当天没有符合条件的发布记录时，返回空结果属于正常成功，不应反复重试。

## 9. 部署后验证

### 9.1 运营后台

1. 打开运营后台生产地址。
2. 使用超级管理员账号登录。
3. 确认工作台、内容任务、内容分发、KOC 资源、数据回收和用户管理页面可以打开。
4. 创建一个普通用户账号并登录，确认普通用户无法看到 KOC 资源库；管理员和超级管理员可以正常访问全部业务模块。
5. 使用一个已授权的飞书表格链接执行“读取表格”，确认标题、正文和图片可读取。
6. 检查已有任务和历史截图仍然存在，确认 D1、R2 没有被替换。

### 9.2 KOC 领取站点

1. 从后台复制一个有效任务领取链接。
2. 在未登录后台的浏览器中打开链接。
3. 确认可以领取笔记、查看标题/正文/图片，并回填发布链接与截图。
4. 确认后台能看到对应的领取、发布和数据回收记录。

### 9.3 自动采集

1. 为测试任务设置一个采集日期。
2. 确认对应采集任务已创建。
3. 到点后检查点赞、收藏、评论、总互动、更新时间和采集状态。
4. 异常数据使用后台“一键补采异常数据”或受鉴权的内部接口补跑。

### 9.4 MCP

1. 不带 `Authorization` 请求 `/api/mcp`，确认返回 `401`。
2. 使用生产 `KOC_MCP_API_KEY` 执行 `tools/list`，确认发现 13 个工具。
3. 调用 `task_list`、`resource_search` 和 `recovery_list`，确认只读查询正常。
4. 使用测试任务调用 `task_get`，核对笔记、领取、发布回填和采集记录。
5. 调用两种导出工具，确认下载链接在有效期内可打开、过期或二次使用后失效，且 Excel 中图片为直接嵌入。
6. 采集和账号补全工具会访问外部正式采集 MCP，只在明确选择测试记录后执行。

## 10. 回滚

优先在 Sites 中选择上一个正常版本重新部署。代码也需要回退时使用：

```bash
git revert <需要撤销的提交>
git push origin main
```

然后按第 6 节重新部署对应站点。不要使用 `git reset --hard` 覆盖共享分支，也不要删除 D1 或 R2 来处理普通代码故障。

## 11. 常见问题

| 现象 | 排查项 |
| --- | --- |
| 超级管理员无法首次登录 | 检查 `SUPER_ADMIN_USERNAME`、`SUPER_ADMIN_PASSWORD` 是否已配置；若系统已有超级管理员，应在后台重置账号密码 |
| MCP 返回 401 | 检查 Agent 请求头是否使用独立的 `KOC_MCP_API_KEY`，不要误用后台或数据采集密钥 |
| MCP 导出链接失效 | 重新调用导出工具生成新链接；链接为一次性且仅保留 15 分钟 |
| KOC 领取页无法访问后台接口 | 检查 `KOC_PORTAL_URL`、领取站点 Origin、后台地址和 CORS |
| 飞书表格读取失败 | 检查飞书 App ID/Secret、应用权限、文档应用授权和表格链接 |
| 自动采集失败 | 检查正式 MCP URL/Key、笔记发布链接、采集计划和 Worker 日志 |
| 图片上传或查看失败 | 检查运营后台 `UPLOADS` R2 绑定 |
| 构建提示 `vinext: command not found` | 在对应站点目录执行 `npm ci` 后重新构建 |
| 定时任务未执行 | 检查生产版本是否包含 Cron、采集日期是否已保存、笔记是否已回填发布链接 |

## 12. 参考资料

- [MCP 部署指南](https://gta.gbotai.cn/ai-team/mcp-project/src/branch/main/docs/MCP%20%E9%83%A8%E7%BD%B2%E6%8C%87%E5%8D%97.md)
- 仓库根目录 `README.md`
- `koc-portal/README.md`
- `.openai/hosting.json`
- `koc-portal/.openai/hosting.json`
