部署文档.md 14 KB

Homelegance Chatbot 生产部署文档

服务器:AlmaLinux 8.10 | Web 服务器:Apache(与主站共用)
访问地址https://www.homelegance.com/chat/
部署路径/redant/web/homelegance-chatbot
数据库:内网独立 PostgreSQL 服务器,Schema chatbot


目录

  1. 环境要求
  2. Node.js 安装
  3. 代码部署
  4. 数据库初始化
  5. 环境变量配置
  6. 构建与启动
  7. PM2 进程管理
  8. Apache 配置
  9. FastAPI ERP 桥接服务
  10. 日常更新流程
  11. 部署验证清单
  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

# 需要服务器能访问外网(如有防火墙限制,先联系 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 和 PM2

bash npm install -g pnpm pm2 pnpm --version pm2 --version


### 2.3 安装 Python(用于 ERP 桥接服务)

bash sudo dnf install -y python3.11 python3.11-pip pip3.11 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` 中的内容添加到主站配置文件的 `<VirtualHost *:443>` 块内即可。

### 8.2 主站配置文件位置

bash

查找主站配置文件

grep -r "ServerName.*homelegance.com" /etc/httpd/conf.d/


### 8.3 添加配置

在主站 `<VirtualHost *:443>` 块内追加:

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

```