# Homelegance Chatbot 生产部署文档 **服务器**:AlmaLinux 8.10 | **Web 服务器**:Apache(与主站共用) **访问地址**:`https://www.homelegance.com/chat/` **部署路径**:`/redant/web/homelegance-chatbot` **数据库**:内网独立 PostgreSQL 服务器,Schema `chatbot` --- ## 目录 1. [环境要求](#1-环境要求) 2. [Node.js 安装](#2-nodejs-安装) 3. [代码部署](#3-代码部署) 4. [数据库初始化](#4-数据库初始化) 5. [环境变量配置](#5-环境变量配置) 6. [构建与启动](#6-构建与启动) 7. [PM2 进程管理](#7-pm2-进程管理) 8. [Apache 配置](#8-apache-配置) 9. [FastAPI ERP 桥接服务](#9-fastapi-erp-桥接服务) 10. [日常更新流程](#10-日常更新流程) 11. [部署验证清单](#11-部署验证清单) 12. [环境变量速查](#12-环境变量速查) --- ## 1. 环境要求 | 项目 | 详情 | |------|------| | 操作系统 | AlmaLinux 8.10 | | Web 服务器 | Apache httpd(主站已有,共用) | | 部署路径 | `/redant/web/homelegance-chatbot` | | Node.js | v20.20.2 LTS | | 包管理器 | pnpm v10.x | | 进程管理 | PM2 | | 数据库 | 内网独立 PostgreSQL 服务器,Schema `chatbot` | | LLM API | Anthropic Claude API(`claude-sonnet-4-6`) | | ERP 桥接 | Python FastAPI,端口 8080,仅本机访问 | --- ## 2. Node.js 安装 ### 2.1 通过 NodeSource 安装 Node.js 20 ```bash # 需要服务器能访问外网(如有防火墙限制,先联系 IT 放行 nodesource.com) curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash - sudo dnf install -y nodejs --allowerasing node --version # 应输出 v20.x.x ``` > **注意**:安装前如有旧版 Node.js(v10),需先卸载: > ```bash > sudo dnf remove -y nodejs npm > ``` ### 2.2 安装 pnpm 和 PM2 ```bash npm install -g pnpm pm2 pnpm --version pm2 --version ``` ### 2.3 安装 Python 依赖(用于 ERP 桥接服务) 系统已有 Python 3.12,直接安装所需包: ```bash python3.12 -V # 确认 Python 3.12.x pip3.12 install fastapi uvicorn asyncpg python-dotenv ``` --- ## 3. 代码部署 ### 3.1 克隆代码 ```bash sudo mkdir -p /redant/web/homelegance-chatbot cd /redant/web sudo git clone https://git.united-us.net/TonyT/chatbot.git homelegance-chatbot sudo chown -R $USER:$USER homelegance-chatbot ``` ### 3.2 安装依赖 ```bash cd /redant/web/homelegance-chatbot pnpm install --frozen-lockfile ``` --- ## 4. 数据库初始化 ### 4.1 在内网 PostgreSQL 服务器上创建数据库和用户 ```sql -- 以管理员身份在 PostgreSQL 服务器执行 CREATE DATABASE homelegance_chat; CREATE USER chatbot_user WITH PASSWORD '<强密码>'; GRANT ALL PRIVILEGES ON DATABASE homelegance_chat TO chatbot_user; -- 连接到 homelegance_chat 库后创建 Schema \c homelegance_chat CREATE SCHEMA chatbot; GRANT ALL ON SCHEMA chatbot TO chatbot_user; ``` ### 4.2 执行 Drizzle 迁移 ```bash cd /redant/web/homelegance-chatbot # 确保 .env.production 中 DATABASE_URL 已正确配置 export DATABASE_URL="postgresql://chatbot_user:<密码>@<内网PG地址>:5432/homelegance_chat" pnpm db:push ``` > Drizzle 会自动在 `chatbot` schema 下创建所有表和枚举类型。 ### 4.3 创建管理员账号 ```bash # 首次部署后,通过 /chat/register 页面注册第一个账号 # 然后在数据库中将其提升为 admin psql -U chatbot_user -h <内网PG地址> -d homelegance_chat -c \ "UPDATE chatbot.users SET role='admin' WHERE email='<管理员邮箱>';" ``` --- ## 5. 环境变量配置 复制模板并填写真实值: ```bash cp /redant/web/homelegance-chatbot/deploy/env.production.example \ /redant/web/homelegance-chatbot/.env.production nano /redant/web/homelegance-chatbot/.env.production ``` **`.env.production` 内容说明:** ```env NODE_ENV=production PORT=3000 # Chatbot 自身数据库(内网 PostgreSQL) DATABASE_URL=postgresql://chatbot_user:<密码>@<内网PG地址>:5432/homelegance_chat # JWT 签名密钥(随机生成64字符) JWT_SECRET= # Anthropic Claude API ANTHROPIC_API_KEY=sk-ant-... # ERP 桥接服务(内网,仅本机访问) ERP_API_URL=http://127.0.0.1:8080 ERP_API_KEY=<内部随机密钥> # Dealer Portal SSO 共享密钥 DEALER_PORTAL_SSO_SECRET=<与Portal约定的JWT签名密钥> ``` --- ## 6. 构建与启动 ```bash cd /redant/web/homelegance-chatbot # 构建前端(输出到 dist/public/)和后端(输出到 dist/index.js) pnpm build # 验证构建产物 ls dist/public/ # 应有 index.html、assets/ 等 ls dist/index.js # 后端入口 ``` --- ## 7. PM2 进程管理 ### 7.1 配置文件 `ecosystem.config.cjs` 已在项目根目录: ```js module.exports = { apps: [{ name: 'homelegance-chat', script: './dist/index.js', env_file: '.env.production', instances: 1, autorestart: true, max_memory_restart: '512M', }] } ``` ### 7.2 启动与自启 ```bash cd /redant/web/homelegance-chatbot pm2 start ecosystem.config.cjs pm2 save # 配置系统启动时自动拉起(只需执行一次) pm2 startup systemd # 按提示执行输出的 sudo 命令 ``` ### 7.3 常用命令 ```bash pm2 list # 查看进程状态 pm2 logs homelegance-chat # 查看实时日志 pm2 logs homelegance-chat --lines 100 # 查看最近100行日志 pm2 restart homelegance-chat # 重启服务 pm2 stop homelegance-chat # 停止服务 ``` --- ## 8. Apache 配置 ### 8.1 说明 Chatbot 挂载在 **www.homelegance.com 主站的 `/chat/` 路径下**,无需新建 VirtualHost。将 `deploy/homelegance-chat.conf` 中的内容添加到主站配置文件的 `` 块内即可。 ### 8.2 主站配置文件位置 ```bash # 查找主站配置文件 grep -r "ServerName.*homelegance.com" /etc/httpd/conf.d/ ``` ### 8.3 添加配置 在主站 `` 块内追加: ```apache # ── Chatbot API:代理到 Node.js :3000 ───────────────────────── # ProxyPass 必须在 Alias 之前声明 ProxyPreserveHost On ProxyPass /chat/api http://127.0.0.1:3000/api ProxyPassReverse /chat/api http://127.0.0.1:3000/api # ── Chatbot 静态文件:由 Apache 直接服务 ─────────────────────── Alias /chat /redant/web/homelegance-chatbot/dist/public Options -Indexes AllowOverride None Require all granted # ── SPA fallback:/chat/* 非文件请求返回 index.html ─────────── RewriteEngine On RewriteCond %{REQUEST_URI} ^/chat/ RewriteCond %{REQUEST_URI} !/chat/api/ RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^ /redant/web/homelegance-chatbot/dist/public/index.html [L] ``` ### 8.4 重载 Apache ```bash # 检查配置语法 sudo apachectl configtest # 重载(不中断现有连接) sudo systemctl reload httpd ``` ### 8.5 确认所需模块已启用 ```bash httpd -M | grep -E "proxy|rewrite|alias" # 应看到:proxy_module, proxy_http_module, rewrite_module, alias_module ``` 如未启用: ```bash sudo dnf install -y mod_proxy_http # 在 /etc/httpd/conf.modules.d/ 中取消对应模块的注释 sudo systemctl restart httpd ``` --- ## 9. FastAPI ERP 桥接服务 > **用途**:提供从 Chatbot 查询 ERP PostgreSQL 数据的只读接口,仅绑定 127.0.0.1,Apache 不代理此端口,外网不可达。 ### 9.1 创建服务目录 ```bash sudo mkdir -p /redant/web/erp-bridge sudo chown $USER:$USER /redant/web/erp-bridge cd /redant/web/erp-bridge ``` ### 9.2 创建 `.env` 文件 ```bash cat > /redant/web/erp-bridge/.env << 'EOF' ERP_DATABASE_URL=postgresql://chatbot_readonly:<密码>@:5432/ ERP_API_KEY=<与chatbot共享的内部随机密钥> PORT=8080 EOF ``` ### 9.3 创建 `main.py` ```python from fastapi import FastAPI, HTTPException, Header, Depends from contextlib import asynccontextmanager from pydantic import BaseModel from typing import Optional import asyncpg, os from dotenv import load_dotenv load_dotenv() DB_POOL = None API_KEY = os.getenv("ERP_API_KEY") @asynccontextmanager async def lifespan(app: FastAPI): global DB_POOL DB_POOL = await asyncpg.create_pool( os.getenv("ERP_DATABASE_URL"), min_size=2, max_size=10 ) yield await DB_POOL.close() app = FastAPI(lifespan=lifespan) def verify_key(x_api_key: str = Header()): if x_api_key != API_KEY: raise HTTPException(status_code=401, detail="Unauthorized") # 健康检查 @app.get("/health") async def health(): return {"status": "ok"} # 产品目录查询 class CatalogQuery(BaseModel): category: Optional[str] = None model: Optional[str] = None description: Optional[str] = None limit: int = 20 @app.post("/catalog", dependencies=[Depends(verify_key)]) async def catalog(q: CatalogQuery): # 根据实际 ERP 表结构调整 rows = await DB_POOL.fetch( "SELECT item_code, description, category FROM items WHERE " "($1::text IS NULL OR category ILIKE $1) AND " "($2::text IS NULL OR item_code ILIKE $2) " "LIMIT $3", f"%{q.category}%" if q.category else None, f"%{q.model}%" if q.model else None, q.limit ) return [dict(r) for r in rows] # 订单列表查询 class OrderQuery(BaseModel): so_id: Optional[str] = None customer_name: Optional[str] = None limit: int = 10 @app.post("/orders", dependencies=[Depends(verify_key)]) async def orders(q: OrderQuery): rows = await DB_POOL.fetch( "SELECT so_id, customer_name, status, order_date FROM sales_orders WHERE " "($1::text IS NULL OR so_id ILIKE $1) AND " "($2::text IS NULL OR customer_name ILIKE $2) " "ORDER BY order_date DESC LIMIT $3", f"%{q.so_id}%" if q.so_id else None, f"%{q.customer_name}%" if q.customer_name else None, q.limit ) return [dict(r) for r in rows] # 单个订单详情 @app.get("/orders/{so_id}", dependencies=[Depends(verify_key)]) async def order_detail(so_id: str): row = await DB_POOL.fetchrow( "SELECT * FROM sales_orders WHERE so_id = $1", so_id ) if not row: raise HTTPException(status_code=404, detail="Order not found") items = await DB_POOL.fetch( "SELECT item_code, qty, price FROM sales_order_items WHERE so_id = $1", so_id ) return {**dict(row), "items": [dict(i) for i in items]} # 库存查询 class StockQuery(BaseModel): model: str limit: int = 20 @app.post("/stock", dependencies=[Depends(verify_key)]) async def stock(q: StockQuery): rows = await DB_POOL.fetch( "SELECT item_code, warehouse, qty_available FROM stock WHERE item_code ILIKE $1 LIMIT $2", f"%{q.model}%", q.limit ) return [dict(r) for r in rows] ``` ### 9.4 DBA 创建 ERP 只读账号 ```sql -- 在 ERP PostgreSQL 服务器执行 CREATE USER chatbot_readonly WITH PASSWORD '<密码>'; GRANT SELECT ON TABLE items, contacts, sales_orders, sales_order_items, stock TO chatbot_readonly; ``` ### 9.5 Systemd 服务(`deploy/erp-bridge.service`) ```bash sudo cp /redant/web/homelegance-chatbot/deploy/erp-bridge.service \ /etc/systemd/system/erp-bridge.service sudo systemctl daemon-reload sudo systemctl enable --now erp-bridge sudo systemctl status erp-bridge ``` **验证:** ```bash curl http://127.0.0.1:8080/health # 应返回:{"status":"ok"} ``` --- ## 10. 日常更新流程 ```bash cd /redant/web/homelegance-chatbot # 1. 拉取最新代码 git pull origin master # 2. 更新依赖(仅新增包时需要) pnpm install --frozen-lockfile # 3. 重新构建 pnpm build # 4. 重启 Node.js(Apache 静态文件无需重启) pm2 restart homelegance-chat # 5. 验证 pm2 logs homelegance-chat --lines 20 curl -I https://www.homelegance.com/chat/ ``` --- ## 11. 部署验证清单 | 检查项 | 命令/方式 | 预期结果 | |--------|-----------|----------| | Node.js 版本 | `node --version` | v20.x.x | | pnpm 版本 | `pnpm --version` | v10.x.x | | PM2 进程运行 | `pm2 list` | `homelegance-chat` online | | Node.js 监听端口 | `ss -tlnp \| grep 3000` | 0.0.0.0:3000 | | FastAPI 运行 | `curl http://127.0.0.1:8080/health` | `{"status":"ok"}` | | Apache 配置语法 | `sudo apachectl configtest` | Syntax OK | | 首页可访问 | `curl -I https://www.homelegance.com/chat/` | HTTP 200 | | API 可访问 | `curl https://www.homelegance.com/chat/api/trpc` | JSON 响应 | | 数据库连接 | `pm2 logs` 中无 DB 错误 | 无报错 | | HTTPS 证书 | 浏览器访问 | 无证书警告 | --- ## 12. 环境变量速查 | 变量名 | 说明 | 示例 | |--------|------|------| | `NODE_ENV` | 运行环境 | `production` | | `PORT` | Node.js 监听端口 | `3000` | | `DATABASE_URL` | Chatbot 自身 PG 连接串 | `postgresql://chatbot_user:pwd@db:5432/homelegance_chat` | | `JWT_SECRET` | JWT 签名密钥(64字符随机) | `openssl rand -hex 32` | | `ANTHROPIC_API_KEY` | Claude API 密钥 | `sk-ant-...` | | `ERP_API_URL` | ERP 桥接服务地址 | `http://127.0.0.1:8080` | | `ERP_API_KEY` | ERP 桥接内部密钥 | 随机字符串 | | `DEALER_PORTAL_SSO_SECRET` | Dealer Portal SSO 共享密钥 | HS256 签名密钥 | --- ## 附录 ### Git 仓库 ``` https://git.united-us.net/TonyT/chatbot.git ``` ### deploy/ 目录文件说明 | 文件 | 用途 | |------|------| | `homelegance-chat.conf` | Apache 配置片段(添加到主站 VirtualHost) | | `erp-bridge.service` | FastAPI ERP 桥接的 systemd 服务文件 | | `env.production.example` | 环境变量模板 | | `部署文档.md` | 本文档 | ### 目录结构 ``` /redant/web/ ├── homelegance-chatbot/ ← Chatbot 项目 │ ├── dist/ │ │ ├── public/ ← 前端静态文件(Apache 直接服务) │ │ └── index.js ← 后端入口(PM2 管理) │ ├── .env.production ← 环境变量(不提交 Git) │ └── ecosystem.config.cjs ← PM2 配置 └── erp-bridge/ ← FastAPI ERP 桥接服务 ├── main.py └── .env ```