# SME-OmniStore 前后端分离、不同服务器部署说明

本文按 **后端 API、管理后台 Admin、商城前台 Store 分别部署在不同服务器** 来写。重点是 IP / 域名配置、端口放行、CORS 和代理设置。

## 1. 推荐拓扑

示例：

| 服务 | 服务器 | 对外端口 | 示例地址 |
|---|---|---:|---|
| Backend API | API 服务器 | 8000 或 443 | `http://API_SERVER_IP:8000` |
| Admin 管理后台 | Admin 前端服务器 | 3001 或 443 | `http://ADMIN_SERVER_IP:3001` |
| Store 商城前台 | Store 前端服务器 | 3002 或 443 | `http://STORE_SERVER_IP:3002` |
| MySQL | 数据库服务器，建议只内网 | 3306 | 只允许 API 服务器访问 |
| Redis，可选 | 缓存服务器，建议只内网 | 6379 | 只允许 API 服务器访问 |

生产环境建议最终统一用域名 + HTTPS：

- API：`https://api.example.com`
- Admin：`https://admin.example.com`
- Store：`https://shop.example.com`

如果暂时用 IP，也可以，但浏览器跨域和 CORS 必须配置准确。

---

## 2. 后端 API 服务器配置

### 2.1 需要修改的文件

#### `backend/.env`

重点字段：

```env
APP_ENV=production
DEBUG=false
SECRET_KEY=请改成强随机字符串

DB_HOST=MYSQL_SERVER_IP
DB_PORT=3306
DB_NAME=sme_omnistore
DB_USER=sme_user
DB_PASSWORD=数据库密码

# 后端 API 对外访问地址
API_BASE_URL=http://API_SERVER_IP:8000

# 允许访问 API 的前端来源，必须写完整 origin，不带尾部斜杠
CORS_ORIGINS=http://ADMIN_SERVER_IP:3001,http://STORE_SERVER_IP:3002

# 初始租户域名；如果用商城域名，填 shop 域名
INIT_TENANT_DOMAIN=STORE_SERVER_IP
```

如果使用域名：

```env
API_BASE_URL=https://api.example.com
CORS_ORIGINS=https://admin.example.com,https://shop.example.com
INIT_TENANT_DOMAIN=shop.example.com
```

> 注意：`main.py` 读取的是 `settings.CORS_ORIGINS`。如果 `backend/app/config.py` 里没有 `CORS_ORIGINS` 字段，需要补上：
>
> ```python
> CORS_ORIGINS: str = "http://ADMIN_SERVER_IP:3001,http://STORE_SERVER_IP:3002"
> ```
>
> 不建议生产环境依赖 `DEBUG=true` 的 `*` 跨域。

#### `backend/app/config.py`

确认这些字段存在或默认值正确：

```python
API_BASE_URL: str = "http://API_SERVER_IP:8000"
CORS_ORIGINS: str = "http://ADMIN_SERVER_IP:3001,http://STORE_SERVER_IP:3002"
DB_HOST: str = "MYSQL_SERVER_IP"
```

如果只通过 `.env` 配置，代码默认值不重要，但字段名必须存在。

### 2.2 启动后端

```bash
cd backend
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
alembic upgrade head
uvicorn main:app --host 0.0.0.0 --port 8000
```

生产建议用 systemd 或 supervisor 托管。

### 2.3 后端防火墙放行

API 服务器需要放行：

```bash
# SSH，按实际端口
ufw allow 22/tcp
# 或本项目服务器常用 22022
ufw allow 22022/tcp

# API 端口，如果不用 Nginx/HTTPS 反代
ufw allow 8000/tcp

# 如果使用 Nginx + HTTPS
ufw allow 80/tcp
ufw allow 443/tcp
```

如果 MySQL 在同一台 API 服务器且只本机访问，不要对公网开放 3306。

如果 MySQL 独立部署，只允许 API 服务器访问：

```bash
ufw allow from API_SERVER_IP to any port 3306 proto tcp
```

Redis 同理：

```bash
ufw allow from API_SERVER_IP to any port 6379 proto tcp
```

---

## 3. Admin 管理后台服务器配置

Admin 是 Vue/Vite SPA。

### 3.1 需要修改的文件

#### `frontend/admin/.env`

开发或直接构建时读取：

```env
VITE_API_BASE=http://API_SERVER_IP:8000/api
VITE_USE_MOCK=false
```

如果使用域名：

```env
VITE_API_BASE=https://api.example.com/api
VITE_USE_MOCK=false
```

#### `frontend/admin/.env.production`

如果用项目里的 `proxy.js` 启动生产服务，需要配置：

```env
API_BASE_URL=http://API_SERVER_IP:8000
PORT=3001
```

域名版：

```env
API_BASE_URL=https://api.example.com
PORT=3001
```

#### `frontend/admin/vite.config.js`

开发模式 `npm run dev` 时，Vite proxy 会读 `VITE_API_BASE`：

```js
const API_TARGET = (process.env.VITE_API_BASE || 'http://127.0.0.1:8000/api').replace(/\/api$/, '')
```

所以开发时只要 `.env` 的 `VITE_API_BASE` 正确即可。

#### `frontend/admin/proxy.js`

生产用 `node proxy.js` 时读取：

```js
const BACKEND_URL = process.env.API_BASE_URL || 'http://127.0.0.1:8000';
```

所以生产必须设置 `API_BASE_URL=http://API_SERVER_IP:8000`。

### 3.2 构建与启动

```bash
cd frontend/admin
npm ci
npm run build

# 方式 A：使用项目 proxy.js，同时托管 dist + 代理 /api
API_BASE_URL=http://API_SERVER_IP:8000 PORT=3001 node proxy.js

# 方式 B：使用 Nginx 托管 dist，不用 proxy.js
# Nginx 中把 /api 反代到 http://API_SERVER_IP:8000/api
```

### 3.3 Admin 服务器防火墙

如果直接暴露 3001：

```bash
ufw allow 3001/tcp
```

如果用 Nginx：

```bash
ufw allow 80/tcp
ufw allow 443/tcp
```

---

## 4. Store 商城前台服务器配置

Store 是 Nuxt 3 SSR。

### 4.1 需要修改的文件

#### `frontend/store/.env`

当前项目里默认可能是：

```env
NUXT_PUBLIC_API_BASE=http://127.0.0.1:8000/api
NUXT_PUBLIC_SITE_URL=http://127.0.0.1:3000
NUXT_PUBLIC_SITE_NAME=SME-OmniStore
NUXT_PUBLIC_USE_MOCK=false
```

不同服务器部署时必须改：

```env
NUXT_PUBLIC_API_BASE=http://API_SERVER_IP:8000/api
NUXT_PUBLIC_SITE_URL=http://STORE_SERVER_IP:3002
NUXT_PUBLIC_SITE_NAME=SME-OmniStore
NUXT_PUBLIC_USE_MOCK=false
```

域名版：

```env
NUXT_PUBLIC_API_BASE=https://api.example.com/api
NUXT_PUBLIC_SITE_URL=https://shop.example.com
NUXT_PUBLIC_SITE_NAME=SME-OmniStore
NUXT_PUBLIC_USE_MOCK=false
```

#### `frontend/store/.env.production`

生产构建时也要同步改：

```env
NUXT_PUBLIC_API_BASE=http://API_SERVER_IP:8000/api
NUXT_PUBLIC_SITE_URL=http://STORE_SERVER_IP:3002
NUXT_PUBLIC_SITE_NAME=SME-OmniStore
NUXT_PUBLIC_USE_MOCK=false
```

#### `frontend/store/nuxt.config.ts`

重点：当前 `nitro.routeRules` 里有一处硬编码：

```ts
'/api/**': { proxy: { to: 'http://127.0.0.1:8000/api/**' } },
```

如果 Store 和 Backend 不在同一台服务器，必须改成后端服务器地址：

```ts
'/api/**': { proxy: { to: 'http://API_SERVER_IP:8000/api/**' } },
```

更推荐改成环境变量，避免以后换 IP 再改代码：

```ts
const backendInternalUrl = process.env.NUXT_BACKEND_INTERNAL_URL || 'http://API_SERVER_IP:8000'

export default defineNuxtConfig({
  // ...
  nitro: {
    compressPublicAssets: true,
    routeRules: {
      '/api/**': { proxy: { to: `${backendInternalUrl}/api/**` } },
    },
  },
})
```

然后在 `.env.production` 加：

```env
NUXT_BACKEND_INTERNAL_URL=http://API_SERVER_IP:8000
```

### 4.2 两种 API 调用模式

#### 模式 A：浏览器直接访问后端 API

```env
NUXT_PUBLIC_API_BASE=http://API_SERVER_IP:8000/api
```

要求：
- API 服务器 8000 对用户浏览器可访问
- 后端 `CORS_ORIGINS` 必须包含 Store 地址

#### 模式 B：浏览器访问 Store 同源 `/api`，由 Nuxt 代理到后端

```env
NUXT_PUBLIC_API_BASE=/api
NUXT_BACKEND_INTERNAL_URL=http://API_SERVER_IP:8000
```

要求：
- 用户浏览器只访问 Store 服务器
- Store 服务器能访问 API 服务器 8000
- 对浏览器来说同源，一般更少 CORS 问题

生产更推荐 **模式 B**。

### 4.3 构建与启动

```bash
cd frontend/store
npm ci
npm run build

HOST=0.0.0.0 PORT=3002 node .output/server/index.mjs
```

### 4.4 Store 服务器防火墙

如果直接暴露 3002：

```bash
ufw allow 3002/tcp
```

如果用 Nginx：

```bash
ufw allow 80/tcp
ufw allow 443/tcp
```

如果使用 Nuxt 代理模式，Store 服务器必须能访问：

```bash
curl http://API_SERVER_IP:8000/health
```

---

## 5. Nginx 反向代理示例

### 5.1 API 域名 `api.example.com`

部署在 API 服务器：

```nginx
server {
    listen 80;
    server_name api.example.com;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
```

### 5.2 Admin 域名 `admin.example.com`

如果 Admin 用 Nginx 托管 dist：

```nginx
server {
    listen 80;
    server_name admin.example.com;

    root /data/www/admin/dist;
    index index.html;

    location /api/ {
        proxy_pass http://API_SERVER_IP:8000/api/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location / {
        try_files $uri $uri/ /index.html;
    }
}
```

此时 Admin `.env.production` 可用：

```env
VITE_API_BASE=/api
VITE_USE_MOCK=false
```

### 5.3 Store 域名 `shop.example.com`

Nuxt SSR 由 Node 跑在 3002：

```nginx
server {
    listen 80;
    server_name shop.example.com;

    location / {
        proxy_pass http://127.0.0.1:3002;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
```

---

## 6. 必须放行 / 必须禁止的端口

### API 服务器

| 端口 | 是否放行 | 说明 |
|---:|---|---|
| 22 / 22022 | 按需 | SSH 管理 |
| 8000 | 如果无 Nginx 则放行 | FastAPI |
| 80 / 443 | 如果用 Nginx/HTTPS 则放行 | 推荐生产使用 |
| 3306 | 不建议公网放行 | MySQL，只允许 API 服务器访问 |
| 6379 | 不建议公网放行 | Redis，只允许 API 服务器访问 |

### Admin 服务器

| 端口 | 是否放行 | 说明 |
|---:|---|---|
| 3001 | 无 Nginx 时放行 | Admin UI |
| 80 / 443 | 用 Nginx 时放行 | 推荐生产使用 |

### Store 服务器

| 端口 | 是否放行 | 说明 |
|---:|---|---|
| 3002 | 无 Nginx 时放行 | Nuxt SSR |
| 80 / 443 | 用 Nginx 时放行 | 推荐生产使用 |

---

## 7. 部署前后检查命令

### 后端检查

```bash
curl http://API_SERVER_IP:8000/health
curl http://API_SERVER_IP:8000/docs
curl http://API_SERVER_IP:8000/api/store/products?page=1&page_size=1
```

### Admin 检查

```bash
curl http://ADMIN_SERVER_IP:3001/
# 浏览器打开后登录后台，并确认 Network 中 /api/auth/login 指向正确 API
```

### Store 检查

```bash
curl http://STORE_SERVER_IP:3002/
curl http://STORE_SERVER_IP:3002/products
curl http://STORE_SERVER_IP:3002/api/sitemap.xml
```

如果 Store 使用直连 API：

```bash
curl http://API_SERVER_IP:8000/api/sitemap.xml
```

如果 Store 使用 Nuxt 代理 `/api`：

```bash
curl http://STORE_SERVER_IP:3002/api/sitemap.xml
```

---

## 8. 最容易出错的地方

1. **Store 的 `nuxt.config.ts` 仍写死 `127.0.0.1:8000`**  
   不同服务器时必须改成 API 服务器 IP 或环境变量。

2. **Admin `.env` 和 `.env.production` 不一致**  
   开发用 `VITE_API_BASE`，生产 `proxy.js` 用 `API_BASE_URL`。

3. **Backend `CORS_ORIGINS` 没包含前端地址**  
   用浏览器直连 API 时会跨域失败。

4. **API 服务器没监听 `0.0.0.0`**  
   必须：
   ```bash
   uvicorn main:app --host 0.0.0.0 --port 8000
   ```

5. **防火墙只本机能访问**  
   服务器上 `curl 127.0.0.1:8000` 成功，不代表外部浏览器能访问。要用：
   ```bash
   curl http://API_SERVER_IP:8000/health
   ```

6. **`VITE_USE_MOCK` / `NUXT_PUBLIC_USE_MOCK` 不能是 true**  
   否则前端显示成功但不请求真实后端。

---

## 9. 快速配置清单

替换以下占位符：

- `API_SERVER_IP`
- `ADMIN_SERVER_IP`
- `STORE_SERVER_IP`
- `MYSQL_SERVER_IP`
- 可选：`api.example.com / admin.example.com / shop.example.com`

必须检查这些文件：

```text
backend/.env
backend/app/config.py
frontend/admin/.env
frontend/admin/.env.production
frontend/admin/vite.config.js
frontend/admin/proxy.js
frontend/store/.env
frontend/store/.env.production
frontend/store/nuxt.config.ts
```

必须检查这些端口：

```text
API:   8000 或 443
Admin: 3001 或 443
Store: 3002 或 443
MySQL: 3306 只允许 API 服务器访问
Redis: 6379 只允许 API 服务器访问
```
