服务器:AlmaLinux 8.10 | Web 服务器:Apache(与主站共用)
访问地址:https://www.homelegance.com/chat/
部署路径:/redant/web/homelegance-chatbot
数据库:内网独立 PostgreSQL 服务器,Schema chatbot
| 项目 | 详情 |
|---|---|
| 操作系统 | 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,仅本机访问 |
# 需要服务器能访问外网(如有防火墙限制,先联系 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),需先卸载:
> sudo dnf remove -y nodejs npm > ``` ### 2.2 安装 pnpm 和 PM2bash 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
export DATABASE_URL="postgresql://chatbot_user:<密码>@<内网PG地址>:5432/homelegance_chat"
pnpm db:push
> Drizzle 会自动在 `chatbot` schema 下创建所有表和枚举类型。
### 4.3 创建管理员账号
bash
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
DATABASE_URL=postgresql://chatbot_user:<密码>@<内网PG地址>:5432/homelegance_chat
JWT_SECRET=
ANTHROPIC_API_KEY=sk-ant-...
ERP_API_URL=http://127.0.0.1:8080 ERP_API_KEY=<内部随机密钥>
DEALER_PORTAL_SSO_SECRET=<与Portal约定的JWT签名密钥>
---
## 6. 构建与启动
bash cd /redant/web/homelegance-chatbot
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
### 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` 中的内容添加到主站配置文件的 `<VirtualHost *:443>` 块内即可。
### 8.2 主站配置文件位置
bash
grep -r "ServerName.*homelegance.com" /etc/httpd/conf.d/
### 8.3 添加配置
在主站 `<VirtualHost *:443>` 块内追加:
apache
ProxyPreserveHost On ProxyPass /chat/api http://127.0.0.1:3000/api ProxyPassReverse /chat/api http://127.0.0.1:3000/api
Alias /chat /redant/web/homelegance-chatbot/dist/public
Options -Indexes
AllowOverride None
Require all granted
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"
如未启用:
bash sudo dnf install -y mod_proxy_http
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
---
## 10. 日常更新流程
bash cd /redant/web/homelegance-chatbot
git pull origin master
pnpm install --frozen-lockfile
pnpm build
pm2 restart homelegance-chat
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
```