Browse Source

Mount chatbot at /chat/ under www.homelegance.com

- vite.config.ts: set base='/chat/' for correct asset paths
- App.tsx: use wouter Router base='/chat' for SPA routing
- homelegance-chat.conf: rewrite as /chat/ location block inside
  existing www.homelegance.com VirtualHost (no separate subdomain)
- Add 部署文档.md: full Markdown deployment guide replacing Word doc

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tony T 2 tuần trước cách đây
mục cha
commit
51dddaa9bc
4 tập tin đã thay đổi với 568 bổ sung46 xóa
  1. 5 3
      client/src/App.tsx
  2. 28 43
      deploy/homelegance-chat.conf
  3. 534 0
      deploy/部署文档.md
  4. 1 0
      vite.config.ts

+ 5 - 3
client/src/App.tsx

@@ -1,7 +1,7 @@
 import { Toaster } from "@/components/ui/sonner";
 import { TooltipProvider } from "@/components/ui/tooltip";
 import NotFound from "@/pages/NotFound";
-import { Route, Switch } from "wouter";
+import { Router, Route, Switch } from "wouter";
 import ErrorBoundary from "./components/ErrorBoundary";
 import { ThemeProvider } from "./contexts/ThemeContext";
 import DashboardLayout from "./components/DashboardLayout";
@@ -18,7 +18,7 @@ import Playground from "./pages/Playground";
 import Analytics from "./pages/Analytics";
 import DataSources from "./pages/DataSources";
 
-function Router() {
+function AppRoutes() {
   return (
     <Switch>
       {/* Public pages */}
@@ -74,7 +74,9 @@ function App() {
       <ThemeProvider defaultTheme="light">
         <TooltipProvider>
           <Toaster />
-          <Router />
+          <Router base="/chat">
+            <AppRoutes />
+          </Router>
         </TooltipProvider>
       </ThemeProvider>
     </ErrorBoundary>

+ 28 - 43
deploy/homelegance-chat.conf

@@ -1,43 +1,28 @@
-# Apache Virtual Host — Homelegance Chatbot
-# Place at: /etc/httpd/conf.d/homelegance-chat.conf
-# Reload: sudo systemctl reload httpd
-
-<VirtualHost *:80>
-    ServerName chat.homelegance.com
-    Redirect permanent / https://chat.homelegance.com/
-</VirtualHost>
-
-<VirtualHost *:443>
-    ServerName chat.homelegance.com
-
-    SSLEngine on
-    SSLCertificateFile    /etc/ssl/certs/homelegance.crt
-    SSLCertificateKeyFile /etc/ssl/private/homelegance.key
-
-    Header always set X-Content-Type-Options nosniff
-    Header always set X-Frame-Options SAMEORIGIN
-    Header always set Referrer-Policy strict-origin-when-cross-origin
-
-    # Static files served directly by Apache
-    DocumentRoot /redant/web/homelegance-chatbot/dist/public
-    <Directory /redant/web/homelegance-chatbot/dist/public>
-        Options -Indexes
-        AllowOverride None
-        Require all granted
-    </Directory>
-
-    # API requests proxied to Node.js
-    ProxyPreserveHost On
-
-    ProxyPass /api http://127.0.0.1:3000/api
-    ProxyPassReverse /api http://127.0.0.1:3000/api
-
-    # SPA fallback — non-file requests return index.html
-    RewriteEngine On
-    RewriteCond %{REQUEST_URI} !^/api/
-    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-f
-    RewriteRule ^ /index.html [L]
-
-    ErrorLog  /var/log/httpd/homelegance-chat-error.log
-    CustomLog /var/log/httpd/homelegance-chat-access.log combined
-</VirtualHost>
+# Homelegance Chatbot — Apache 配置片段
+# 将以下内容添加到 www.homelegance.com 的 <VirtualHost *:443> 块内
+# 文件位于:/etc/httpd/conf.d/homelegance.com.conf(或对应的主站配置文件)
+# 修改后执行:sudo systemctl reload httpd
+#
+# 所需模块(通常已启用):
+#   mod_proxy, mod_proxy_http, mod_rewrite, mod_alias
+
+# ── Chatbot API:代理到 Node.js :3000 ────────────────────────────────────────
+# ProxyPass 必须在 Alias 之前声明,否则 Alias 会优先匹配 /chat/api/
+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
+<Directory /redant/web/homelegance-chatbot/dist/public>
+    Options -Indexes
+    AllowOverride None
+    Require all granted
+</Directory>
+
+# ── 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]

+ 534 - 0
deploy/部署文档.md

@@ -0,0 +1,534 @@
+# 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 桥接服务)
+
+```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=<openssl rand -hex 32>
+
+# 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
+<Directory /redant/web/homelegance-chatbot/dist/public>
+    Options -Indexes
+    AllowOverride None
+    Require all granted
+</Directory>
+
+# ── 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:<密码>@<ERP_PG地址>:5432/<ERP数据库名>
+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
+```

+ 1 - 0
vite.config.ts

@@ -164,6 +164,7 @@ export default defineConfig({
   envDir: path.resolve(import.meta.dirname),
   root: path.resolve(import.meta.dirname, "client"),
   publicDir: path.resolve(import.meta.dirname, "client", "public"),
+  base: "/chat/",
   build: {
     outDir: path.resolve(import.meta.dirname, "dist/public"),
     emptyOutDir: true,