# Graduate OS 最终版 · Ubuntu 22.04 / 宝塔部署与运维手册

> 最终整合日期：2026-08-28  
> 适用发布包：`Graduate-OS-Final-Integrated-20260828.zip`  
> 部署方式：宝塔 Linux + Nginx + Node.js 22 + PM2 + MySQL 8  
> 正式运行：`node .next/standalone/server.js`

这份文档已经把初始部署、Phase 2、Paper Radar 热修复、AI Compatible/长论文可靠性热修复，以及实际服务器部署时出现过的问题全部整合。**使用最终整合源码时，不需要再按历史聊天逐个安装旧补丁。**

---

# 1. 最终架构

```text
浏览器
  ↓ HTTPS
Nginx / 宝塔站点
  ↓ 可选但强烈推荐：Basic Auth
Graduate OS 登录
  ↓
Next.js 16 standalone
127.0.0.1:3000
  ↓
MySQL 8 + Prisma 6
```

论文 PDF：

```text
/www/wwwroot/graduate-os/storage/papers
```

必须通过登录后的受保护 API 下载，不能让 Nginx 直接公开 `/storage/`。

AI / Search：

```text
Graduate OS
  ├─ OpenAI Official
  ├─ OpenAI Compatible / Sub2API / 自定义中转
  ├─ SerpApi → Google Scholar API
  ├─ Crossref
  └─ OpenAlex
```

---

# 2. 最终发布包已经合并的修复

最终源码已经包含：

```text
Graduate-OS Phase 2 Final Source
+
Paper Radar Trial Hotfix
+
Paper Radar Relevance & Tags Hotfix
+
Paper AI Reliability Final Hotfix
+
实际生产服务器运行时修复
```

其中包括：

1. 后台 AI Provider / Search Provider 配置。
2. AES-256-GCM API Key 加密，`SECRET_ENCRYPTION_KEY` 只存在 `.env`。
3. OpenAI Official 与 OpenAI Compatible 分流。
4. Sub2API 作为 OpenAI Compatible 使用。
5. OpenAI Compatible JSON Object + 本地 JSON.parse + Zod 校验。
6. 修复第三方 `strict json_schema` 400 错误。
7. 长论文输入预算、章节优先、精简重试、少量分块与 524 超时处理。
8. AI 分析状态自动轮询，成功/失败无需手工 F5。
9. Paper Radar 清空结果、状态保持、分数排序、新获取 Badge、研究分类标签。
10. 秋招精简表单、面试联动、题库 DOCX/PDF 导入与练习。
11. `server-only` 已正式加入依赖，不再需要部署后手工补装。
12. PM2 已内置项目根 `.env` 加载。
13. PM2 已内置 `--dns-result-order=ipv4first`，解决真实部署中第三方中转 IPv6 被 Cloudflare 403 的情况。
14. `.env.example` 中 `AI_ENABLED` / `SCHOLAR_ENABLED` 已使用正确的 `true`。

---

# 3. 部署前准备

准备：

- Ubuntu 22.04 服务器
- 宝塔 Linux 面板
- 一个域名或子域名，例如 `xjtu.example.com`
- MySQL 8
- Node.js 22 LTS（建议 >= 22.13）
- PM2
- Nginx
- 最终源码 ZIP

推荐服务器：

```text
最低试运行：2C / 2G / 40G
推荐长期使用：2C / 4G 或以上
```

PDF 和 AI 分析会消耗内存；大量 PDF 时磁盘容量需要按实际增加。

---

# 4. 宝塔安装运行环境

进入：

```text
宝塔 → 软件商店
```

安装：

```text
Nginx
MySQL 8
Node.js 版本管理器
```

Node.js 选择：

```text
22 LTS
```

终端确认：

```bash
node -v
npm -v
```

Node 应为：

```text
v22.x.x
```

安装 PM2：

```bash
npm install -g pm2
pm2 -v
```

---

# 5. 创建宝塔网站和 SSL

先解析域名 A 记录到服务器 IPv4。

宝塔：

```text
网站 → 添加站点
```

建议：

```text
域名：xjtu.example.com
根目录：/www/wwwroot/graduate-os
PHP：纯静态 / 不使用 PHP
数据库：本步骤先不创建也可以
```

然后：

```text
网站 → xjtu.example.com → SSL → Let's Encrypt
```

申请证书并开启强制 HTTPS。

## 很重要：宝塔 SSL 配置不要乱覆盖

宝塔站点配置里通常有：

```text
#CERT-APPLY-CHECK--START
...
#CERT-APPLY-CHECK--END

#SSL-START
#error_page 404/404.html;
...
#SSL-END
```

**不要修改宝塔提示不能修改的 SSL 注释 404 行。**

如果宝塔提示：

```text
请勿修改SSL相关配置中注释的404规则
```

说明你改到了受保护位置。恢复该行，把自定义反代/安全配置放到正常 Nginx 区域，或直接用宝塔 GUI 的「反向代理 / 访问限制」。

---

# 6. 创建 MySQL 数据库

宝塔：

```text
数据库 → MySQL → 添加数据库
```

建议：

```text
数据库名：graduate_os
用户名：graduate_os
字符集：utf8mb4
权限：本地服务器
```

建议数据库密码只用字母数字的强随机值，避免 `DATABASE_URL` URL 编码问题。

可以生成：

```bash
openssl rand -hex 24
```

记录数据库密码。

**3306 不需要开放公网。**

---

# 7. 上传最终源码

进入：

```text
宝塔 → 文件 → /www/wwwroot/graduate-os
```

上传最终源码 ZIP，解压到当前目录。

最终必须直接看到：

```text
/www/wwwroot/graduate-os/package.json
/www/wwwroot/graduate-os/package-lock.json
/www/wwwroot/graduate-os/app/
/www/wwwroot/graduate-os/components/
/www/wwwroot/graduate-os/lib/
/www/wwwroot/graduate-os/prisma/
/www/wwwroot/graduate-os/.env.example
/www/wwwroot/graduate-os/ecosystem.config.cjs
```

错误：

```text
/www/wwwroot/graduate-os/Graduate-OS/package.json
```

正确：

```text
/www/wwwroot/graduate-os/package.json
```

如果多套一层目录，用宝塔文件管理器把内层文件移动到项目根目录。

---

# 8. 创建运行目录

终端：

```bash
cd /www/wwwroot/graduate-os
mkdir -p storage/papers logs
chmod 750 storage storage/papers logs
```

不要：

```bash
chmod -R 777 storage
```

---

# 9. 创建 `.env`

宝塔文件管理器复制：

```text
.env.example
```

为：

```text
.env
```

也可以：

```bash
cd /www/wwwroot/graduate-os
cp .env.example .env
```

然后在宝塔文件编辑器打开：

```text
/www/wwwroot/graduate-os/.env
```

推荐模板：

```dotenv
DATABASE_URL="mysql://graduate_os:你的数据库密码@127.0.0.1:3306/graduate_os"

# 官方 OpenAI 可留空；如果从网站后台配置 Provider，不必在这里填写真实 Key。
OPENAI_API_KEY=""
OPENAI_MODEL="gpt-5.6"
OPENAI_PDF_DETAIL="high"

# 用于加密数据库中的 AI / SerpApi Key。生成一次后永久保留。
SECRET_ENCRYPTION_KEY="填写64位十六进制字符串"

# 后台配置 SerpApi 时可以留空；这里保留兼容回退。
SERPAPI_API_KEY=""

ADMIN_USERNAME="你的系统管理员用户名"
ADMIN_PASSWORD="首次创建管理员时使用的强密码"

SESSION_SECRET="填写随机长字符串"
APP_URL="https://xjtu.example.com"
UPLOAD_DIR="/www/wwwroot/graduate-os/storage/papers"
MAX_PDF_SIZE_MB="40"

AI_ENABLED="true"
SCHOLAR_ENABLED="true"
RADAR_MAX_RESULTS_PER_KEYWORD="20"

PORT="3000"
BACKUP_DIR="/backup/graduate-os"
```

## 9.1 生成 SESSION_SECRET

```bash
openssl rand -hex 48
```

复制输出到：

```dotenv
SESSION_SECRET="..."
```

## 9.2 生成 SECRET_ENCRYPTION_KEY

```bash
openssl rand -hex 32
```

复制输出到：

```dotenv
SECRET_ENCRYPTION_KEY="..."
```

### 这个值以后不要重新生成

后台保存的：

- OpenAI API Key
- OpenAI Compatible / Sub2API Key
- SerpApi Key

会使用它加密。更换该密钥会导致已经保存的密钥无法解密。

## 9.3 `true` 拼写必须正确

正确：

```dotenv
AI_ENABLED="true"
SCHOLAR_ENABLED="true"
```

错误：

```dotenv
AI_ENABLED="ture"
SCHOLAR_ENABLED="ture"
```

`ture` 会被程序视为未启用。

## 9.4 权限

```bash
cd /www/wwwroot/graduate-os
chmod 600 .env
```

---

# 10. 安装 Node 依赖

```bash
cd /www/wwwroot/graduate-os
npm ci
```

最终整合包已经把真实部署中缺失过的：

```text
server-only@0.0.1
```

正式写入 `package.json` 与 `package-lock.json`，因此新部署**不需要再手动 `npm install server-only`**。

检查：

```bash
npm ls server-only --depth=0
```

应看到：

```text
server-only@0.0.1
```

如果 `npm ci` 只出现 `npm WARN`，通常不是失败；真正失败会出现 `npm ERR!`。

---

# 11. 初始化 Prisma 数据库

执行：

```bash
cd /www/wwwroot/graduate-os
npx prisma generate
npx prisma migrate deploy
npx prisma migrate status
```

当前最终包包含：

```text
20260824000000_init
20260826000000_phase2_task1_ai_provider
20260826010000_phase2_task2_search_provider
20260826020000_phase2_task4_interview_link
20260826030000_phase2_task5b_question_analysis
20260826040000_phase2_task6_question_practice
```

最后应看到数据库 schema 已是最新。

## 常见数据库错误

### P1000 / Authentication failed

数据库账号或密码错误。

检查 `.env`：

```dotenv
DATABASE_URL="mysql://graduate_os:正确密码@127.0.0.1:3306/graduate_os"
```

### P1001

MySQL 未启动、地址/端口错误。

### P1045

数据库用户名或密码错误。

**绝对不要为了解决连接错误执行：**

```bash
npx prisma migrate reset
npx prisma db push --force-reset
```

---

# 12. 创建唯一管理员

执行：

```bash
cd /www/wwwroot/graduate-os
npm run admin:create
```

创建成功后，在 `.env` 中把：

```dotenv
ADMIN_PASSWORD=""
```

清空，避免首次密码长期留在配置中。

系统不开放注册，只有一个管理员。

---

# 13. 构建 standalone

```bash
cd /www/wwwroot/graduate-os
npm run typecheck
npm run lint
npm run build
```

`npm run build` 已包含：

```text
Prisma generate
Next.js build
standalone 资源整理
public/static 同步
```

检查：

```bash
test -f .next/standalone/server.js && echo "server OK"
test -d .next/standalone/.next/static && echo "static OK"
test -d .next/standalone/public && echo "public OK"
```

应该看到：

```text
server OK
static OK
public OK
```

---

# 14. PM2 启动

最终版项目根目录已经自带：

```text
ecosystem.config.cjs
```

它已经整合真实部署修复：

```text
node --env-file=/www/wwwroot/graduate-os/.env
NODE_OPTIONS=--dns-result-order=ipv4first
```

第一次启动：

```bash
cd /www/wwwroot/graduate-os
pm2 start ecosystem.config.cjs
pm2 save
pm2 startup
```

`pm2 startup` 会打印一条命令，再复制执行一次，然后：

```bash
pm2 save
```

验证：

```bash
pm2 status
pm2 logs graduate-os --lines 100
curl -I http://127.0.0.1:3000/login
```

必须使用：

```text
node .next/standalone/server.js
```

不要改成：

```text
next start
```

因为 `output: "standalone"` 使用 `next start` 会产生错误。

---

# 15. 为什么 PM2 要显式加载 `.env`

真实部署过程中出现过：

```text
请先配置 SECRET_ENCRYPTION_KEY
```

即使项目根 `.env` 已经填写。

原因是 standalone 运行时不能依赖“碰巧自动读取”项目根 `.env`。

最终 `ecosystem.config.cjs` 已内置：

```text
--env-file=/www/wwwroot/graduate-os/.env
```

所以**不要在部署完成后删除这项**。

---

# 16. 为什么最终版优先 IPv4

真实测试中：

```text
IPv6 → zzshu.cc / Cloudflare → HTTP 403 Blocked
IPv4 → 同一 API → HTTP 200 / OK
```

因此 PM2 已内置：

```text
NODE_OPTIONS=--dns-result-order=ipv4first
```

这不是 API Key 修复，而是第三方 API 网络兼容修复。

如果未来使用其他 OpenAI Compatible 服务，也可以保留该设置。

---

# 17. 配置 Nginx 反向代理

最简单方式：

```text
宝塔 → 网站 → 你的域名 → 反向代理 → 添加反向代理
```

填写：

```text
名称：graduate-os
目标 URL：http://127.0.0.1:3000
发送域名：$host
缓存：关闭
```

然后在反向代理高级配置确认至少有：

```nginx
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;
proxy_set_header X-Forwarded-Host $host;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
proxy_buffering off;
```

## 私有 PDF 保护

站点中必须保证：

```nginx
location ^~ /storage/ {
    deny all;
    return 404;
}
```

如果宝塔反代已经生成 `location /`，不要再在同一 `server` 内重复写第二个 `location /`。

项目里的 `NGINX.conf.example` 是完整参考，不建议在宝塔已自动管理 SSL 时整段替换站点文件。

---

# 18. 外层 Basic Auth

推荐使用宝塔自带：

```text
网站 → 访问限制 / 访问控制 → 密码访问
```

用户名可以自定义，不必叫 `graduate_outer`。

如果面板版本没有该功能，可以手工：

```bash
apt update
apt install -y apache2-utils
mkdir -p /www/server/pass
htpasswd -c /www/server/pass/graduate-os.htpasswd 你想要的外层用户名
chmod 640 /www/server/pass/graduate-os.htpasswd
```

然后在合适的 Nginx 访问区域使用：

```nginx
auth_basic "Graduate OS Private";
auth_basic_user_file /www/server/pass/graduate-os.htpasswd;
```

### 更换外层用户名

最简单重新创建：

```bash
htpasswd -c /www/server/pass/graduate-os.htpasswd 新用户名
```

然后重载 Nginx。

外层 Basic Auth 和 Graduate OS `/login` 管理员密码必须使用不同密码。

---

# 19. 第一次进入后台

浏览器：

```text
https://xjtu.example.com
```

正确顺序：

```text
外层 Basic Auth
  ↓
Graduate OS /login
  ↓
后台
```

---

# 20. 配置 AI Provider

进入：

```text
设置 → AI 服务 → 新增 AI 服务
```

## OpenAI Official

填写：

```text
服务名称：OpenAI Official
服务类型：OpenAI Official
API Key：你的 OpenAI API Key
Model：你的账号可用模型
启用：是
默认：按需要
```

## OpenAI Compatible / Sub2API

例如：

```text
服务名称：Sub2API
服务类型：OpenAI Compatible
Base URL：https://api.example.com/v1
API Key：sk-...
Model：中转站实际模型 ID
启用：是
默认：是
```

### Base URL 只填写 API 根路径

正确：

```text
https://api.example.com/v1
```

错误：

```text
https://api.example.com/v1/chat/completions
```

程序会自己调用 endpoint。

点击：

```text
测试连接
```

成功后再正式使用。

---

# 21. AI Compatible 最终兼容策略

历史真实错误：

```text
400 Invalid schema for response_format
Extra required key 'confidence'
```

最终版已改为：

```text
OpenAI Official
→ 官方 Structured Outputs

OpenAI Compatible
→ Chat Completions
→ JSON Object / Plain JSON fallback
→ Graduate OS 本地 JSON.parse
→ Graduate OS 本地 Zod 校验
```

因此第三方网关不需要完整实现 OpenAI strict json_schema。

`confidence` 等字段仍然保留，由服务器本地校验。

如果上线后仍看到老的：

```text
paper_analysis_text_fallback Invalid schema
```

通常说明：

```text
旧源码没有被完整覆盖
或
覆盖后没有重新 npm run build
或
PM2 仍然运行旧 .next
```

---

# 22. 长 PDF 与 524 超时

历史真实错误：

```text
status_code=524
The origin web server did not return a complete response within the 120-second Proxy Read Timeout window.
```

最终版针对 OpenAI Compatible 已加入：

- 章节优先文本整理
- 输入预算
- 更短的降级重试
- 少量分块分析
- 超时识别
- 有限重试
- 最终失败写 `FAILED`

分析过程中页面会自动轮询状态。

如果仍超时：

1. 稍后重试。
2. 检查中转站是否拥堵。
3. 先用较短 PDF 验证 Provider。
4. 查看 `/logs/error.log` 和 AI 调用记录。

不要因为第三方 524 去无限增加 Nginx 超时时间；524 是第三方上游限制。

---

# 23. 配置 SerpApi

进入：

```text
设置 → 搜索服务 → 配置 SerpApi
```

填写 API Key → 测试连接 → 保存。

系统继续保留：

```text
Crossref
OpenAlex
```

即使 SerpApi 暂时不可用，Crossref / OpenAlex 仍可工作。

系统不直接抓 Google Scholar HTML。

---

# 24. 必须填写研究档案

进入：

```text
设置 → 研究档案
```

建议填写：

- 专业
- 研究方向
- 课题名称
- 研究关键词
- 材料
- 实验方法
- 仿真方法
- 当前研究问题
- 当前论文题目
- 研究目标

### 为什么必须填

Radar AI 相关度评分需要研究上下文。

如果 Research Profile 为空，可能出现论文相关度全部偏低甚至出现异常 0 分。

推荐先填研究档案，再运行 Radar。

---

# 25. Paper Radar 使用

进入：

```text
论文雷达
```

建议使用精确英文组合词，例如：

```text
"wood honeycomb" "out-of-plane compression"
"densified wood" honeycomb compression
"wood honeycomb" buckling compression
```

最终版支持：

- 相关度 0–100
- 默认高分到低分
- 同分时新获取排前
- `✦ 新获取`
- 已忽略
- 已加入论文库
- 清空检索结果
- 研究标签分类

研究分类：

```text
木基蜂窝
材料致密化
几何设计
面外压缩
屈曲失效
比强度/承载
吸能/动态
```

### 清空检索结果

只清：

```text
RadarResult
```

不会删除：

```text
RadarKeyword
Paper 论文库
AI Provider
SerpApi
研究档案
```

---

# 26. 论文库与 AI 深度分析

进入：

```text
论文库 → 上传 PDF
```

单文件最大值由：

```dotenv
MAX_PDF_SIZE_MB="40"
```

控制。

Nginx 建议：

```text
client_max_body_size 50m
```

上传后：

```text
PDF保存
→ 文本提取
→ 元数据校验
→ AI分析
→ 保存结构化结果
```

如果 AI 失败：

```text
PDF 仍然保留
processStatus = FAILED
```

页面无需 F5 就会显示失败状态；可以点击「重新分析」。

---

# 27. 秋招使用

投递记录最终表单已精简：

保留：

```text
企业名称
岗位名称
企业性质
行业
工作地点
岗位类别（实习 / 正式工）
投递状态
投递日期
岗位链接
投递链接
薪资
内推码
联系人
Offer 信息
```

UI 已删除/隐藏：

```text
投递渠道
学历要求
简历版本
备注
```

状态进入对应阶段时才显示：

```text
笔试时间
一面时间
二面时间
三面时间
HR 面时间
```

面试记录与投递记录通过 `jobApplicationId` 联动。

---

# 28. 面试题库

支持：

```text
手动新增
DOCX 导入
PDF 导入
AI 结构化解析
导入预览
编辑 / 取消
重复检测
批量入库
```

练习模式支持：

```text
企业
岗位
类别
标签
```

提交回答后：

```text
标准答案
解析
AI点评
AI参考评分
```

没有 AI Provider 时，普通答题和标准答案仍可使用。

---

# 29. Paper Radar 宝塔计划任务

先查 Node 路径：

```bash
command -v node
command -v npm
dirname "$(command -v node)"
```

宝塔 Node 常见不是 `/usr/bin/npm`，可能是：

```text
/www/server/nodejs/v22.x.x/bin
```

进入：

```text
宝塔 → 计划任务 → Shell 脚本
```

例如每天 08:00：

```bash
export PATH=/www/server/nodejs/v22.x.x/bin:$PATH
cd /www/wwwroot/graduate-os
npm run radar:update >> /www/wwwroot/graduate-os/logs/radar.log 2>&1
```

把 Node 路径换成你服务器真实结果。

测试后查看：

```bash
tail -n 100 /www/wwwroot/graduate-os/logs/radar.log
```

---

# 30. 备份

手动：

```bash
cd /www/wwwroot/graduate-os
chmod +x backup.sh
bash backup.sh /www/wwwroot/graduate-os
```

建议宝塔每天执行一次。

正式长期使用建议至少备份：

```text
MySQL
storage/papers
必要配置模板
```

`.env` 含秘密，不要放进公开备份或发布 ZIP；如果你自己做加密离线备份，可以单独保管。

---

# 31. 更新最终版源码

未来收到新的完整源码 ZIP：

1. 宝塔文件管理器上传并覆盖代码。
2. 不覆盖 `.env`、`storage/`。
3. 终端：

```bash
cd /www/wwwroot/graduate-os
npm ci
npx prisma generate
npx prisma migrate deploy
npm run build
pm2 restart graduate-os --update-env
pm2 save
```

如果明确说明某个热修复：

```text
没有依赖变化
没有 migration
```

可以按热修复文档跳过 `npm ci` / `prisma migrate deploy`。

**本最终整合包是从零部署包，不需要再安装历史热修复。**

---

# 32. 最常见故障排查

## 32.1 网站 502

```bash
pm2 status
pm2 logs graduate-os --lines 200
curl -I http://127.0.0.1:3000/login
```

判断：

```text
PM2 offline → Node / 构建 / 环境变量
PM2 online 但本机3000失败 → 应用问题
本机3000正常但域名502 → Nginx反代问题
```

## 32.2 PM2 报 next start / standalone

错误思路：

```text
next start
```

正确：

```text
node .next/standalone/server.js
```

最终 `ecosystem.config.cjs` 已修好，不要改回去。

## 32.3 `Cannot find module 'server-only'`

最终包已经修复依赖。

先：

```bash
npm ci
npm ls server-only --depth=0
```

如果使用的不是最终整合包，旧版本才可能需要手工安装。

## 32.4 后台提示 `请先配置 SECRET_ENCRYPTION_KEY`

确认 `.env`：

```dotenv
SECRET_ENCRYPTION_KEY="64位十六进制字符串"
```

确认最终 `ecosystem.config.cjs` 未被旧文件覆盖。

重启：

```bash
pm2 restart graduate-os --update-env
pm2 save
```

## 32.5 中转站测试显示认证失败，但 Key 确定正确

先直接强制 IPv4 测试：

```bash
curl -4 -i "https://你的中转域名/v1/chat/completions" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer 你的测试Key" \
  -d '{"model":"你的模型ID","messages":[{"role":"user","content":"Reply only with OK."}],"max_tokens":16}'
```

如果 IPv4 200，而普通请求 403 Cloudflare，说明是 IPv6/WAF。

最终 PM2 已内置 IPv4-first。

测试 Key 用完后如果曾公开展示，应该作废并换新。

## 32.6 AI 400 Invalid schema

最终版已经修复。

若仍出现：

```text
检查是否覆盖了最新 lib/ai/* / lib/papers/*
重新 npm run build
重新 pm2 restart graduate-os --update-env
```

## 32.7 AI 524 / timeout

最终版会尝试精简/分块并最终返回失败状态。

如果仍失败：

```text
中转站拥堵或模型响应过慢
稍后重试
先用短 PDF 验证
```

## 32.8 页面一直显示 AI 分析中

最终版每 3 秒轮询。

如果仍不变：

```bash
pm2 logs graduate-os --lines 120
```

并确认浏览器已加载最新 build（Ctrl+F5）。

## 32.9 Radar 全部 0 分

先检查研究档案是否为空：

```text
设置 → 研究档案
```

研究方向/关键词没有填写时，AI 无法正确判断相关性。

## 32.10 Radar 结果很多但不相关

使用更精确组合词，不要单独搜索：

```text
wood
honeycomb
compression
```

推荐：

```text
"wood honeycomb" "out-of-plane compression"
```

## 32.11 PDF 413

Nginx：

```text
client_max_body_size 50m
```

`.env`：

```dotenv
MAX_PDF_SIZE_MB="40"
```

## 32.12 登录循环

确认：

```dotenv
APP_URL="https://你的正式域名"
```

Nginx 必须传：

```nginx
proxy_set_header X-Forwarded-Proto $scheme;
```

`SESSION_SECRET` 不能每次重启改变。

---

# 33. Nginx / 宝塔最重要的安全原则

不要：

```text
直接公网开放3000
直接公网开放3306
把storage/papers放public
把.env放到ZIP分享
把真实API Key写README
```

建议：

```text
HTTPS
Basic Auth
Graduate OS 登录
MySQL localhost
PM2 localhost:3000
.env chmod 600
```

---

# 34. 禁止执行的危险命令

```bash
npx prisma migrate reset
npx prisma db push --force-reset
rm -rf /www/wwwroot/graduate-os
rm -rf /www/wwwroot/graduate-os/storage
rm /www/wwwroot/graduate-os/.env
chmod -R 777 /www/wwwroot/graduate-os
```

不要随意删除：

```text
prisma/migrations/
```

---

# 35. 最终上线验收

逐项确认：

- [ ] Ubuntu 22.04
- [ ] Node.js 22
- [ ] MySQL 8 正常
- [ ] `npm ci` 成功
- [ ] `server-only` 已安装
- [ ] `.env` 已填写且权限 600
- [ ] `SECRET_ENCRYPTION_KEY` 已生成并保存
- [ ] `AI_ENABLED="true"`
- [ ] `SCHOLAR_ENABLED="true"`
- [ ] Prisma migrations 全部 deploy
- [ ] 管理员创建成功
- [ ] `ADMIN_PASSWORD=""`
- [ ] `npm run typecheck` 通过
- [ ] `npm run lint` 通过
- [ ] `npm run build` 通过
- [ ] `.next/standalone/server.js` 存在
- [ ] PM2 `graduate-os online`
- [ ] `curl -I 127.0.0.1:3000/login` 有响应
- [ ] Nginx HTTPS 正常
- [ ] 外层 Basic Auth 正常
- [ ] 内层 Graduate OS 登录正常
- [ ] 3000 未公网开放
- [ ] 3306 未公网开放
- [ ] `/storage/` 公网不可访问
- [ ] AI Provider 测试连接成功
- [ ] SerpApi 测试连接成功
- [ ] 研究档案已经填写
- [ ] Radar 能检索并按分数排序
- [ ] Radar 状态标签/研究分类正常
- [ ] PDF 上传正常
- [ ] 短 PDF AI 分析成功
- [ ] 长 PDF 可完成或明确 FAILED，不永久卡住
- [ ] 秋招投递 CRUD 正常
- [ ] 投递与面试联动正常
- [ ] DOCX/PDF 面试题导入正常
- [ ] 练习与 AI 点评正常
- [ ] Radar 计划任务测试成功
- [ ] 备份手动测试成功

完成以上项目后，这个最终版即可作为长期运行基线。
