# yzj_reply 客户独立部署版

> **云之家自动回复机器人 - 多实例版**
> 版本：V1.1.6-customer ｜ 发布日期：2026-09-01

本安装包由开发者 Jeff 提供，用于客户在自有服务器上独立部署 `yzj_reply` 自动回复服务。

V1.1.6 主要升级（vs V1.1.5）：
- 🆕 支持多用户实例同时运行（systemd template 方案）
- 🆕 内置「实例软停用」机制，长期不活跃自动休眠
- 🆕 Cookie 更新后自动恢复，无需手动重启
- 🆕 管理员可主动代发私信给任意云之家用户
- 🆕 重新整理了部署文档，匹配当前真实架构

---

## 📌 你需要准备什么

在开始安装前，请准备好以下信息：

| # | 项目 | 说明 |
|---|------|------|
| 1 | 服务器 | Ubuntu 24.04 LTS，已用 root 登录 |
| 2 | 域名 | 已解析到服务器公网 IP（例如 `v.accopower.com`） |
| 3 | 邮箱 | 用于 Let's Encrypt HTTPS 证书通知 |
| 4 | 云之家账号 | V11 旗舰版或以上，需有扫描权限 |
| 5 | 服务器资源 | 建议 ≥ 2 核 4 GB；多实例时按 0.5 GB/实例叠加 |

---

## 🏗️ 架构概览

V1.1.6 是一个**多实例**部署方案。单台服务器可以跑多个云之家用户，每个用户一个独立实例。

```
┌─────────────────────────────────────────────────────┐
│  nginx（443）                                         │
│    /yzj/            → portal:8080（注册/登录/管理）      │
│    /yzj-dashboard/  → dashboard:2688（管理员仪表盘）       │
│    /yzj-user/<port> → admin:<port>（每个用户的 admin）  │
│    /yzj-guide       → 静态指南页（HTML）                  │
└─────────────────────────────────────────────────────┘
                         ↓
┌─────────────────────────────────────────────────────┐
│  内部服务（systemd 管理）                                │
│                                                       │
│  yzj-reply-portal.service      端口 8080（全局）        │
│  yzj-reply-dashboard.service   端口 2688（全局）        │
│  yzj-reply-bot@<user_X>.service    每用户一个 bot      │
│  yzj-reply-admin@<user_X>.service  每用户一个 admin    │
│  yzj-reply-reenable-watch.{service,timer}   每 5 分钟  │
│  yzj-reply-inactive-detect.{service,timer}  每天 03:00 │
└─────────────────────────────────────────────────────┘
                         ↓
                 /opt/yzj-reply/instances/<user_X>/
                 ├── data/    # 该用户的 cookies、state、KB
                 └── kb/      # 该用户的知识库文件
```

每个用户实例独立占用一个端口（默认 10001-15000 区间）。

---

## 🚀 安装步骤

### 第 1 步：上传安装包到服务器

在**你本地电脑**上执行（替换为你的服务器地址）：

```bash
scp yzj_reply_v1.1.6-customer_2026-09-01.tar.gz root@v.accopower.com:/root/
```

### 第 2 步：SSH 登录服务器

```bash
ssh root@v.accopower.com
```

### 第 3 步：解压安装包

```bash
cd /root
tar -xzf yzj_reply_v1.1.6-customer_2026-09-01.tar.gz
cd yzj_reply_v1.1.6-customer
```

### 第 4 步：编辑环境配置

```bash
cp config.example.env .env
vim .env
```

必填项：

| 变量 | 说明 | 示例 |
|------|------|------|
| `DOMAIN` | 你的域名 | `v.accopower.com` |
| `PROTOCOL` | 协议 | `https` |
| `ADMIN_EMAIL` | Let's Encrypt 通知邮箱 | `[email protected]` |
| `ADMIN_USERNAME` | 管理员登录用户名 | `admin` |
| `ADMIN_PASSWORD` | 管理员登录密码 | 至少 8 位、含字母数字 |

其他变量保持默认值即可（已根据本机架构调好）。

### 第 5 步：运行一键安装脚本

```bash
sudo bash install.sh
```

脚本会自动完成：

- ✅ 安装 Node.js 22、nginx、certbot
- ✅ 创建 `yzjreply` 系统用户
- ✅ 复制代码到 `/opt/yzj-reply/`
- ✅ 注册 systemd 全局服务（portal / dashboard）
- ✅ 注册 systemd template（bot/admin @user_X）
- ✅ 注册两个 timer（re-enable-watcher / inactive-detector）
- ✅ 配置 nginx 反向代理
- ✅ 申请 Let's Encrypt HTTPS 证书
- ✅ 启动所有服务

**整个过程约 5-10 分钟**（取决于网速）。

### 第 6 步：首次登录

打开浏览器，访问你的域名：

```
https://v.accopower.com/yzj/
```

使用你 `.env` 中配置的 `ADMIN_USERNAME` / `ADMIN_PASSWORD` 登录。

---

## 👥 添加云之家用户实例

部署完成后，第一个用户需要这样添加：

### 方式 A：通过 portal 自动注册（推荐）

1. 访问 `https://v.accopower.com/yzj/`
2. 用管理员账号登录
3. 「用户管理」→「新增用户」
4. 填入云之家用户信息
5. 系统会自动：
   - 创建 `instances/user_<id>/` 目录
   - 启动 `yzj-reply-bot@user_<id>.service` 和 `yzj-reply-admin@user_<id>.service`
   - 分配端口（在 `INSTANCE_PORT_MIN`-`INSTANCE_PORT_MAX` 范围内）
   - 更新 nginx 配置 + reload

### 方式 B：手动添加（高级）

```bash
# 1. 创建实例目录
sudo -u yzjreply mkdir -p /opt/yzj-reply/instances/user_<id>/{data,kb}

# 2. 复制模板配置
sudo -u yzjreply cp /opt/yzj-reply/instances/.template/config.json \
                  /opt/yzj-reply/instances/user_<id>/

# 3. 启动服务（systemd 会自动分配端口）
sudo systemctl start yzj-reply-bot@user_<id>.service
sudo systemctl start yzj-reply-admin@user_<id>.service

# 4. 查看分配的端口
sudo systemctl status yzj-reply-admin@user_<id>.service
```

---

## 📱 配置云之家自动回复

每个用户实例独立配置：

### 1. 扫码登录云之家

1. 访问 `https://v.accopower.com/yzj-user/<port>/`（`<port>` 是该实例分配的端口）
2. 用该用户的 admin 账号登录
3. 「账号管理」→「📱 扫码登录云之家」
4. 用云之家 App 扫描二维码
5. 登录成功后，Cookie 会自动保存到 `instances/user_<id>/data/cookies.json`

### 2. 添加关键词和知识库

1. 「关键词管理」→ 添加触发关键词（如 "你好"、"怎么用"）
2. 「知识库管理」→ 添加 Q/A 问答内容
3. （可选）「LLM 配置」→ 填入 API Key

### 3. 启动机器人

回到 admin 首页，点击「启动 Bot」即可。

---

## ⚙️ 运维自动化（V1.1.6 新增）

三个脚本协同工作，实现「无人值守」：

### 1. `scripts/send-as-admin.js` - 管理员代发工具

**用途**：以管理员身份给任意云之家用户发私信（不需要对方的 bot 跑着）。

```bash
node /opt/yzj-reply/scripts/send-as-admin.js <myName或实例名> <toUserId> "<消息>"

# 例：用"周炜"这个实例的身份，给 userId 12345 发消息
node /opt/yzj-reply/scripts/send-as-admin.js "周炜" "12345" "你好，这是一条测试消息"
```

**适用场景**：
- 给长期不活跃用户发提醒
- 给从未注册过的用户主动联系
- 调试时手工触发消息

### 2. `scripts/re-enable-watcher.js` + timer - 自动恢复守护

**用途**：检测到 `disabled=true` 的实例 cookie 被扫码更新过 → 自动启用。

- 调度：`yzj-reply-reenable-watch.timer`（每 5 分钟）
- 状态文件：`/opt/yzj-reply/data/re-enable-watcher-state.json`
- 日志：`/opt/yzj-reply/data/re-enable-watcher.log`

**工作机制**：
1. 扫描所有 `config.disabled=true` 的实例
2. 对比 `cookies.json.savedAt` 与 baseline
3. cookie 新于 baseline → 自动改 `disabled=false` + `systemctl restart bot`

**无需人工介入**，用户重新扫码后 5 分钟内 bot 自动恢复。

### 3. `scripts/inactive-detector.js` + timer - 长期不活跃检测器

**用途**：自动软停用 ≥7 天无活动的实例，节省资源。

- 调度：`yzj-reply-inactive-detect.timer`（每天凌晨 3 点）
- 状态文件：`/opt/yzj-reply/data/inactive-detector-state.json`
- 日志：`/opt/yzj-reply/data/inactive-detector.log`
- 黑名单：`PROTECTED_INSTANCES=['user_']`（管理员本人永不被停）

**工作机制**：
1. 扫描所有活跃实例的 state.json
2. 取「最新活动时间」（排除 lastRunTime，那是 bot 启动时间不是用户活跃时间）
3. 超过 7 天无活动 → 进 7 天观察期
4. 观察期结束仍无活动 → 自动 `disabled=true`
5. 用户再次扫码 → re-enable-watcher 5 分钟内恢复

### 完整闭环

```
活跃实例
   ↓ (7 天无活动)
观察期
   ↓ (再 7 天无活动)
disabled: true（bot 跳过整轮轮询，不发 API 请求）
   ↓
用户扫码更新 cookie
   ↓
re-enable-watcher 5 分钟内检测到
   ↓
disabled: false + restart bot
   ↓
活跃（state 更新）→ 不再被 inactive-detector 误判
```

### 手动启用/禁用某个实例

```bash
# 软停用（推荐：bot 不发 API 请求，但配置和 KB 保留）
echo '{"disabled": true}' | sudo -u yzjreply \
  jq '.disabled = true' \
  /opt/yzj-reply/instances/user_<id>/data/config.json > /tmp/c.json
sudo -u yzjreply mv /tmp/c.json /opt/yzj-reply/instances/user_<id>/data/config.json
sudo systemctl restart yzj-reply-bot@user_<id>.service

# 重新启用
sudo -u yzjreply jq '.disabled = false' \
  /opt/yzj-reply/instances/user_<id>/data/config.json > /tmp/c.json
sudo -u yzjreply mv /tmp/c.json /opt/yzj-reply/instances/user_<id>/data/config.json
sudo systemctl restart yzj-reply-bot@user_<id>.service
```

---

## 🛠️ 常用运维命令

### 全局服务

```bash
# 查看所有 yzj-reply 服务状态
sudo systemctl status 'yzj-reply-*'

# 重启 portal / dashboard
sudo systemctl restart yzj-reply-portal.service
sudo systemctl restart yzj-reply-dashboard.service

# 查看 portal 日志
sudo journalctl -u yzj-reply-portal -f
```

### 单个用户实例

```bash
# 替换 <id> 为实际的 user_ 后缀
sudo systemctl status yzj-reply-bot@user_<id>.service
sudo systemctl status yzj-reply-admin@user_<id>.service
sudo systemctl restart yzj-reply-bot@user_<id>.service

# 查看 bot 日志
sudo journalctl -u yzj-reply-bot@user_<id> -f

# 查看该实例的 data 目录
ls -la /opt/yzj-reply/instances/user_<id>/data/
```

### 运维脚本日志

```bash
# 自动恢复守护（每 5 分钟）
sudo journalctl -u yzj-reply-reenable-watch.service -n 50 --no-pager
tail -f /opt/yzj-reply/data/re-enable-watcher.log

# 长期不活跃检测（每天 03:00）
sudo journalctl -u yzj-reply-inactive-detect.service -n 50 --no-pager
tail -f /opt/yzj-reply/data/inactive-detector.log

# 手动跑一次（debug）
sudo node /opt/yzj-reply/scripts/re-enable-watcher.js
sudo node /opt/yzj-reply/scripts/inactive-detector.js
```

### 修改配置

```bash
sudo vim /opt/yzj-reply/.env
sudo systemctl restart yzj-reply-portal.service
sudo systemctl restart yzj-reply-dashboard.service
```

---

## ❓ 遇到问题？

### 证书申请失败

```bash
# 检查 80 端口是否被占用
sudo ss -tlnp | grep :80
# 手动申请
sudo certbot --nginx -d v.accopower.com
```

### portal 访问显示 502 Bad Gateway

```bash
sudo systemctl status yzj-reply-portal.service
sudo journalctl -u yzj-reply-portal -n 50 -p err
```

### bot 反复重启（cookie watchdog 失败）

```bash
# 看 bot 日志
sudo journalctl -u yzj-reply-bot@user_<id> -n 100

# 临时手动停掉 bot，等用户重新扫码
sudo systemctl stop yzj-reply-bot@user_<id>.service
```

### 某个实例的 admin 页面打不开

```bash
# 1. 确认 bot 和 admin 都在跑
sudo systemctl status yzj-reply-admin@user_<id>.service

# 2. 确认 nginx 反代路径已配置
sudo grep "user_<id>" /etc/nginx/sites-enabled/itonline.cloud
# 或你域名对应的 nginx 配置

# 3. 如果是新增实例但 nginx 没自动更新，手动 reload
sudo nginx -t
sudo systemctl reload nginx
```

### 忘记 admin 密码

```bash
sudo systemctl stop yzj-reply-portal
sudo rm /opt/yzj-reply/data/.admin-password-state.json
sudo systemctl start yzj-reply-portal
# 重新用 .env 里的 ADMIN_USERNAME / ADMIN_PASSWORD 登录
```

### 禁用自动运维脚本

如果你不想用自动软停用 / 自动恢复功能：

```bash
# 停掉 timer
sudo systemctl disable --now yzj-reply-reenable-watch.timer
sudo systemctl disable --now yzj-reply-inactive-detect.timer

# 手动管理实例
# 详见上文「手动启用/禁用某个实例」
```

---

## 📁 重要文件位置

| 文件/目录 | 用途 |
|-----------|------|
| `/opt/yzj-reply/` | 程序代码 |
| `/opt/yzj-reply/.env` | 环境配置（域名、密钥） |
| `/opt/yzj-reply/config.example.env` | 配置模板 |
| `/opt/yzj-reply/yzj-reply.mjs` | Bot 主程序 |
| `/opt/yzj-reply/yzj-reply-portal.js` | Portal 主程序 |
| `/opt/yzj-reply/yzj-reply-dashboard.js` | Dashboard 主程序 |
| `/opt/yzj-reply/scripts/` | 运维脚本（3 个自动化 + 工具） |
| `/opt/yzj-reply/instances/user_<id>/` | 用户实例数据 |
| `/opt/yzj-reply/instances/user_<id>/data/` | cookies / state / config |
| `/opt/yzj-reply/instances/user_<id>/kb/` | 知识库文件 |
| `/opt/yzj-reply/data/` | 全局状态文件（watcher 日志等）|
| `/etc/systemd/system/yzj-reply*.service` | systemd 服务配置 |
| `/etc/systemd/system/yzj-reply*.timer` | systemd 定时器 |
| `/etc/nginx/sites-enabled/itonline.cloud` | nginx 反向代理配置 |

---

## 🔄 从 V1.1.5 升级到 V1.1.6

如果你已经在用 V1.1.5 单实例版：

1. **不要直接覆盖现有部署**——V1.1.5 是单实例，V1.1.6 是多实例，目录结构变了
2. 建议：新服务器部署 V1.1.6 + 迁移数据（复制 `instances/<原实例>/` 数据到新实例）
3. 迁移完成后，旧服务可以停止但保留作为备份
4. 如需迁移指导，请联系开发者

---

## 🔒 安全建议

1. **修改默认 admin 密码**（首次登录强制修改）
2. **不要把 `.env` 提交到 git**（已在 `.gitignore` 中）
3. **不要把 `instances/user_<id>/data/cookies.json` 提交到 git**（敏感 cookie）
4. **定期备份** `/opt/yzj-reply/instances/` 和 `/opt/yzj-reply/.env`
5. **HTTPS 必须开**（Cookie 在传输中，明文 HTTP 会被中间人窃取）
6. **建议限制 portal/admin 访问 IP**（nginx 配合 `allow` / `deny` 指令）

---

## 📞 技术支持

- **开发者**：Jeff
- **文档版本**：V1.1.6-customer
- **发布日期**：2026-09-01

遇到问题时，请提供以下信息以便排查：

1. 服务器系统版本：`lsb_release -a`
2. 所有 yzj-reply 服务状态：`sudo systemctl status 'yzj-reply-*'`
3. 相关日志：`sudo journalctl -u yzj-reply-<service> -n 100`
4. 实例数据状态：`ls -la /opt/yzj-reply/instances/`

---

**祝使用愉快！** 🦞
