866 lines
23 KiB
Markdown
866 lines
23 KiB
Markdown
# 🔧 DesignerCEP 架构问题修正方案
|
||
|
||
## 问题汇总
|
||
|
||
经过仔细检查,当前架构存在以下 **5 个严重问题**,会导致本地测试 OK 但上线后炸:
|
||
|
||
1. ❌ **CORS 配置错误**:只写 `file://`,CEP 环境实际是 `Origin: null` 或 `cep://`
|
||
2. ❌ **Token 暴露在 URL**:`#/home?token=xxx` 会泄露到日志、分享链接
|
||
3. ❌ **localhost:8000 的硬编码**:文档说 Core 可从 `http://localhost:8000/core/...` 加载,但没说这个服务谁提供
|
||
4. ❌ **Cloudflare 证书方案冲突**:用 Cloudflare 代理时不应该用 Certbot
|
||
5. ❌ **静态文件服务混乱**:FastAPI 和 Nginx 职责不清
|
||
|
||
---
|
||
|
||
## 问题 1: CORS 配置错误
|
||
|
||
### 当前问题
|
||
|
||
```python
|
||
# Server/app/main.py (当前代码)
|
||
origins = [
|
||
"http://localhost:5173",
|
||
"http://localhost:3000",
|
||
"*" # ❌ 这个在生产环境不安全
|
||
]
|
||
|
||
# 部署架构说明.md (第 264 行)
|
||
ALLOWED_ORIGINS = [
|
||
"https://your-domain.com",
|
||
"file://", # ❌ CEP 环境不是 file://
|
||
]
|
||
```
|
||
|
||
**实际情况**:
|
||
- CEP/CEF 环境的 Origin 是 `null` 或 `cep://xxx`
|
||
- 预检 OPTIONS 请求会因为 Origin 不匹配而失败
|
||
- 导致所有 API 调用都会被 CORS blocked
|
||
|
||
### ✅ 修正方案
|
||
|
||
```python
|
||
# Server/app/main.py
|
||
from fastapi import FastAPI, Request
|
||
from fastapi.middleware.cors import CORSMiddleware
|
||
import os
|
||
|
||
app = FastAPI(title=settings.PROJECT_NAME)
|
||
|
||
# 环境判断
|
||
IS_PRODUCTION = os.getenv("ENV", "development") == "production"
|
||
|
||
# CORS 中间件 - 支持 CEP 环境
|
||
if IS_PRODUCTION:
|
||
# 生产环境:严格的 CORS
|
||
origins = [
|
||
"https://your-domain.com",
|
||
"https://www.your-domain.com",
|
||
]
|
||
|
||
app.add_middleware(
|
||
CORSMiddleware,
|
||
allow_origins=origins,
|
||
allow_credentials=True,
|
||
allow_methods=["GET", "POST", "PUT", "DELETE"],
|
||
allow_headers=["*"],
|
||
)
|
||
|
||
# 额外处理 CEP 环境的 Origin: null
|
||
@app.middleware("http")
|
||
async def cep_cors_middleware(request: Request, call_next):
|
||
origin = request.headers.get("origin")
|
||
|
||
# CEP 环境:Origin 是 null 或 cep://
|
||
if origin in ["null", None] or origin.startswith("cep://"):
|
||
response = await call_next(request)
|
||
response.headers["Access-Control-Allow-Origin"] = "*"
|
||
response.headers["Access-Control-Allow-Credentials"] = "true"
|
||
response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"
|
||
response.headers["Access-Control-Allow-Headers"] = "*"
|
||
return response
|
||
|
||
return await call_next(request)
|
||
else:
|
||
# 开发环境:宽松的 CORS
|
||
app.add_middleware(
|
||
CORSMiddleware,
|
||
allow_origins=["*"],
|
||
allow_credentials=True,
|
||
allow_methods=["*"],
|
||
allow_headers=["*"],
|
||
)
|
||
```
|
||
|
||
**关键点**:
|
||
- ✅ 生产环境只允许你的域名
|
||
- ✅ 额外处理 CEP 的 `Origin: null` 和 `cep://` 情况
|
||
- ✅ OPTIONS 预检请求会正常通过
|
||
|
||
---
|
||
|
||
## 问题 2: Token 暴露在 URL
|
||
|
||
### 当前问题
|
||
|
||
```typescript
|
||
// 文档中写的跳转方式(第 178 行)
|
||
/core/1.0.0/#/home?token=xxx&username=xxx&device_id=xxx
|
||
|
||
// 问题:
|
||
// 1. Nginx access.log 会记录 URL(token 泄露)
|
||
// 2. Cloudflare 日志也会记录
|
||
// 3. 用户截图/分享会带上 token
|
||
// 4. 浏览器历史记录会保存 token
|
||
```
|
||
|
||
**实际代码情况**:
|
||
- 登录接口返回 token
|
||
- 前端保存到 localStorage
|
||
- 没有看到 URL 传 token 的代码(好消息!)
|
||
|
||
但**文档写错了**,容易误导开发。
|
||
|
||
### ✅ 修正方案
|
||
|
||
**正确的流程**(代码已经是对的,只需更新文档):
|
||
|
||
```
|
||
1. 用户登录
|
||
POST /api/v1/client/login
|
||
返回: { token, username, version, permissions }
|
||
|
||
2. 前端保存到 localStorage
|
||
localStorage.setItem('token', token)
|
||
localStorage.setItem('username', username)
|
||
localStorage.setItem('auto_login', 'true')
|
||
|
||
3. 跳转到 Core(不带 token)
|
||
/core/1.0.0/#/home
|
||
✅ URL 干净,没有敏感信息
|
||
|
||
4. Core 启动时从 localStorage 读取 token
|
||
const token = localStorage.getItem('token')
|
||
|
||
5. API 请求时通过 Header 传递
|
||
Authorization: Bearer ${token}
|
||
```
|
||
|
||
**更新 Axios 配置**:
|
||
|
||
```typescript
|
||
// Designer/src/api/request.ts
|
||
import axios from 'axios';
|
||
import { config } from '@/config';
|
||
|
||
const request = axios.create({
|
||
baseURL: config.apiBaseUrl,
|
||
timeout: 30000,
|
||
});
|
||
|
||
// 请求拦截器 - 自动添加 token
|
||
request.interceptors.request.use(
|
||
(config) => {
|
||
const token = localStorage.getItem('token');
|
||
if (token) {
|
||
// ✅ 通过 Header 传递 token,不在 URL 里
|
||
config.headers.Authorization = `Bearer ${token}`;
|
||
}
|
||
return config;
|
||
},
|
||
(error) => {
|
||
return Promise.reject(error);
|
||
}
|
||
);
|
||
|
||
// 响应拦截器 - 处理 401 自动跳转登录
|
||
request.interceptors.response.use(
|
||
(response) => response,
|
||
(error) => {
|
||
if (error.response?.status === 401) {
|
||
// Token 过期,清除并跳转登录
|
||
localStorage.removeItem('token');
|
||
localStorage.removeItem('username');
|
||
localStorage.removeItem('auto_login');
|
||
window.location.href = config.shellLoginUrl;
|
||
}
|
||
return Promise.reject(error);
|
||
}
|
||
);
|
||
|
||
export default request;
|
||
```
|
||
|
||
---
|
||
|
||
## 问题 3: localhost:8000 的来源不明
|
||
|
||
### 当前问题
|
||
|
||
```typescript
|
||
// Designer/src/config/index.ts (第 15-16 行)
|
||
apiServer: isDev
|
||
? 'http://127.0.0.1:8000'
|
||
: 'http://127.0.0.1:8000', // ❌ 生产环境也用 localhost?
|
||
|
||
// Designer/src/launcher/utils/updater.ts (第 40 行)
|
||
getCoreUrl(version: string): string {
|
||
return `${this.apiServer}/core/${version}/index.html`;
|
||
}
|
||
|
||
// 部署架构说明.md (第 65 行)
|
||
或直接从服务器加载 (https://your-domain.com/core/1.0.0/)
|
||
```
|
||
|
||
**问题**:
|
||
- 文档说 Core 可能从 `localhost:8000` 加载
|
||
- 但没说这个本地服务**谁提供**?
|
||
- 用户电脑上没有这个服务 → **直接打不开**
|
||
|
||
### ✅ 修正方案
|
||
|
||
**方案 A:完全在线模式(推荐)**
|
||
|
||
Core 始终从服务器加载,不依赖本地服务:
|
||
|
||
```typescript
|
||
// Designer/src/config/index.ts
|
||
export const config = {
|
||
// 后端 API 服务器地址
|
||
apiServer: isDev
|
||
? 'http://127.0.0.1:8000' // 开发环境:本地后端
|
||
: 'https://your-domain.com', // ✅ 生产环境:线上服务器
|
||
|
||
// Shell 登录页面地址
|
||
shellLoginUrl: isDev
|
||
? 'http://localhost:5173/#/login'
|
||
: 'https://your-domain.com/shell/#/login', // ✅ 线上 Shell
|
||
|
||
// 获取 Core 应用加载地址
|
||
getCoreUrl(version: string): string {
|
||
if (isDev) {
|
||
return `http://localhost:5173/`; // 开发环境:Vite dev server
|
||
}
|
||
return `${this.apiServer}/core/${version}/index.html`; // ✅ 线上 Core
|
||
}
|
||
};
|
||
```
|
||
|
||
**方案 B:CEP 扩展离线模式(需要额外开发)**
|
||
|
||
如果要支持离线使用(CEP 扩展从本地加载),需要:
|
||
|
||
1. **CEP 扩展自带本地服务器**:
|
||
```typescript
|
||
// 在 CEP 扩展中启动一个轻量级 HTTP 服务器(使用 Node.js http-server)
|
||
import { spawn } from 'child_process';
|
||
import path from 'path';
|
||
|
||
class LocalServer {
|
||
private server: any;
|
||
|
||
start() {
|
||
const cacheDir = path.join(os.homedir(), 'AppData', 'Roaming', 'DesignerCache');
|
||
|
||
// 启动本地 HTTP 服务器
|
||
this.server = spawn('http-server', [
|
||
cacheDir,
|
||
'-p', '8000',
|
||
'--cors',
|
||
'-c-1' // 禁用缓存
|
||
], {
|
||
cwd: __dirname,
|
||
shell: true
|
||
});
|
||
|
||
console.log('✓ 本地服务器已启动: http://localhost:8000');
|
||
}
|
||
|
||
stop() {
|
||
if (this.server) {
|
||
this.server.kill();
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
2. **检测端口占用并动态分配**:
|
||
```typescript
|
||
import net from 'net';
|
||
|
||
async function getAvailablePort(startPort: number): Promise<number> {
|
||
return new Promise((resolve) => {
|
||
const server = net.createServer();
|
||
server.listen(startPort, () => {
|
||
const port = (server.address() as any).port;
|
||
server.close(() => resolve(port));
|
||
});
|
||
server.on('error', () => {
|
||
resolve(getAvailablePort(startPort + 1));
|
||
});
|
||
});
|
||
}
|
||
|
||
// 使用
|
||
const port = await getAvailablePort(8000);
|
||
```
|
||
|
||
**我的建议**:
|
||
- ✅ **使用方案 A**(完全在线)- 简单可靠
|
||
- ⚠️ 方案 B 需要额外开发,且有端口冲突、权限等问题
|
||
- 📝 **更新文档**,明确说明 Core 从哪里加载
|
||
|
||
---
|
||
|
||
## 问题 4: Cloudflare 证书方案冲突
|
||
|
||
### 当前问题
|
||
|
||
```bash
|
||
# 部署前检查清单.md - 推荐 Certbot
|
||
sudo apt install certbot python3-certbot-nginx
|
||
sudo certbot --nginx -d your-domain.com
|
||
```
|
||
|
||
**实际情况**:
|
||
- 你用的是 **Cloudflare 橙云代理**
|
||
- Cloudflare 已经提供了 SSL/TLS 加密
|
||
- 继续用 Certbot 会遇到:
|
||
- Let's Encrypt 验证失败(Cloudflare 代理了请求)
|
||
- 证书续期问题
|
||
- 真实 IP 暴露风险
|
||
|
||
### ✅ 修正方案
|
||
|
||
**Cloudflare 场景的正确配置**:
|
||
|
||
#### 1. Cloudflare SSL/TLS 模式
|
||
|
||
在 Cloudflare 控制台设置:
|
||
|
||
```
|
||
SSL/TLS → Overview → 选择 "Full (Strict)"
|
||
```
|
||
|
||
**模式说明**:
|
||
- ❌ **Flexible**: Cloudflare → 源站是 HTTP(不安全)
|
||
- ⚠️ **Full**: Cloudflare → 源站是 HTTPS(但不验证证书)
|
||
- ✅ **Full (Strict)**: Cloudflare → 源站是 HTTPS(验证证书)- **推荐**
|
||
|
||
#### 2. 生成 Cloudflare Origin Certificate
|
||
|
||
在 Cloudflare 控制台:
|
||
|
||
```
|
||
SSL/TLS → Origin Server → Create Certificate
|
||
|
||
选项:
|
||
- 私钥类型:RSA (2048)
|
||
- 有效期:15 年
|
||
- 域名:*.your-domain.com, your-domain.com
|
||
|
||
生成后会得到:
|
||
- Origin Certificate (保存为 cloudflare-origin.pem)
|
||
- Private Key (保存为 cloudflare-origin.key)
|
||
```
|
||
|
||
#### 3. 配置 Nginx
|
||
|
||
```nginx
|
||
# /etc/nginx/sites-available/designer-cep
|
||
server {
|
||
listen 443 ssl http2;
|
||
server_name your-domain.com www.your-domain.com;
|
||
|
||
# ✅ 使用 Cloudflare Origin Certificate
|
||
ssl_certificate /etc/nginx/ssl/cloudflare-origin.pem;
|
||
ssl_certificate_key /etc/nginx/ssl/cloudflare-origin.key;
|
||
|
||
# SSL 配置
|
||
ssl_protocols TLSv1.2 TLSv1.3;
|
||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||
ssl_prefer_server_ciphers on;
|
||
|
||
# Cloudflare Real IP(获取真实用户 IP)
|
||
set_real_ip_from 103.21.244.0/22;
|
||
set_real_ip_from 103.22.200.0/22;
|
||
set_real_ip_from 103.31.4.0/22;
|
||
set_real_ip_from 104.16.0.0/13;
|
||
set_real_ip_from 108.162.192.0/18;
|
||
set_real_ip_from 131.0.72.0/22;
|
||
set_real_ip_from 141.101.64.0/18;
|
||
set_real_ip_from 162.158.0.0/15;
|
||
set_real_ip_from 172.64.0.0/13;
|
||
set_real_ip_from 173.245.48.0/20;
|
||
set_real_ip_from 188.114.96.0/20;
|
||
set_real_ip_from 190.93.240.0/20;
|
||
set_real_ip_from 197.234.240.0/22;
|
||
set_real_ip_from 198.41.128.0/17;
|
||
set_real_ip_from 2400:cb00::/32;
|
||
set_real_ip_from 2606:4700::/32;
|
||
set_real_ip_from 2803:f800::/32;
|
||
set_real_ip_from 2405:b500::/32;
|
||
set_real_ip_from 2405:8100::/32;
|
||
set_real_ip_from 2c0f:f248::/32;
|
||
set_real_ip_from 2a06:98c0::/29;
|
||
real_ip_header CF-Connecting-IP;
|
||
|
||
# 其他配置...
|
||
}
|
||
|
||
# HTTP 重定向到 HTTPS
|
||
server {
|
||
listen 80;
|
||
server_name your-domain.com www.your-domain.com;
|
||
return 301 https://$server_name$request_uri;
|
||
}
|
||
```
|
||
|
||
#### 4. 安装证书
|
||
|
||
```bash
|
||
# 1. 创建 SSL 目录
|
||
sudo mkdir -p /etc/nginx/ssl
|
||
sudo chmod 700 /etc/nginx/ssl
|
||
|
||
# 2. 上传证书文件(从本地上传)
|
||
sudo nano /etc/nginx/ssl/cloudflare-origin.pem
|
||
# 粘贴 Origin Certificate
|
||
|
||
sudo nano /etc/nginx/ssl/cloudflare-origin.key
|
||
# 粘贴 Private Key
|
||
|
||
# 3. 设置权限
|
||
sudo chmod 600 /etc/nginx/ssl/*
|
||
|
||
# 4. 测试配置
|
||
sudo nginx -t
|
||
|
||
# 5. 重启 Nginx
|
||
sudo systemctl restart nginx
|
||
```
|
||
|
||
**优势**:
|
||
- ✅ 证书有效期 15 年,不需要续期
|
||
- ✅ 不需要 Certbot 验证
|
||
- ✅ Cloudflare 和源站双重加密
|
||
- ✅ 自动获取真实用户 IP
|
||
|
||
---
|
||
|
||
## 问题 5: 静态文件服务混乱
|
||
|
||
### 当前问题
|
||
|
||
```python
|
||
# Server/app/main.py (第 37-55 行)
|
||
# ❌ FastAPI 直接挂载静态文件目录
|
||
app.mount("/download", StaticFiles(directory="archives"), name="download")
|
||
app.mount("/shell", StaticFiles(directory=str(shell_dir), html=True), name="shell")
|
||
app.mount("/core", StaticFiles(directory=str(designer_cache), html=True), name="core")
|
||
```
|
||
|
||
```nginx
|
||
# 部署架构说明.md (第 284-289 行)
|
||
# ✅ 文档说让 Nginx 处理静态文件
|
||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
|
||
expires 1y;
|
||
add_header Cache-Control "public, immutable";
|
||
}
|
||
```
|
||
|
||
**矛盾**:
|
||
- FastAPI 挂载了静态文件 → API 和静态抢资源
|
||
- 文档说用 Nginx 缓存 → 但请求还是先到 FastAPI
|
||
- 大文件下载(shell.zip)性能差
|
||
|
||
### ✅ 修正方案
|
||
|
||
**明确职责**:
|
||
- **Nginx** → 处理所有静态文件(HTML/JS/CSS/ZIP)
|
||
- **FastAPI** → 只处理 API 请求(/api/v1/...)
|
||
|
||
#### 1. 修改 FastAPI(只留 API)
|
||
|
||
```python
|
||
# Server/app/main.py
|
||
from fastapi import FastAPI
|
||
from fastapi.middleware.cors import CORSMiddleware, Request
|
||
import os
|
||
from app.core.config import settings
|
||
from app.api.v1 import auth, client, admin, analytics, jsx_demo
|
||
from app.db import init_db
|
||
|
||
app = FastAPI(title=settings.PROJECT_NAME)
|
||
|
||
# 环境判断
|
||
IS_PRODUCTION = os.getenv("ENV", "development") == "production"
|
||
|
||
# ========== CORS 配置 ==========
|
||
if IS_PRODUCTION:
|
||
origins = [
|
||
"https://your-domain.com",
|
||
"https://www.your-domain.com",
|
||
]
|
||
|
||
app.add_middleware(
|
||
CORSMiddleware,
|
||
allow_origins=origins,
|
||
allow_credentials=True,
|
||
allow_methods=["GET", "POST", "PUT", "DELETE"],
|
||
allow_headers=["*"],
|
||
)
|
||
|
||
# 处理 CEP 环境的 Origin: null
|
||
@app.middleware("http")
|
||
async def cep_cors_middleware(request: Request, call_next):
|
||
origin = request.headers.get("origin")
|
||
if origin in ["null", None] or origin.startswith("cep://"):
|
||
response = await call_next(request)
|
||
response.headers["Access-Control-Allow-Origin"] = "*"
|
||
response.headers["Access-Control-Allow-Credentials"] = "true"
|
||
response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"
|
||
response.headers["Access-Control-Allow-Headers"] = "*"
|
||
return response
|
||
return await call_next(request)
|
||
else:
|
||
app.add_middleware(
|
||
CORSMiddleware,
|
||
allow_origins=["*"],
|
||
allow_credentials=True,
|
||
allow_methods=["*"],
|
||
allow_headers=["*"],
|
||
)
|
||
|
||
# ========== API 路由 ==========
|
||
app.include_router(auth.router, prefix=f"{settings.API_V1_STR}/auth", tags=["authentication"])
|
||
app.include_router(client.router, prefix=f"{settings.API_V1_STR}/client", tags=["client"])
|
||
app.include_router(admin.router, prefix=f"{settings.API_V1_STR}/admin", tags=["admin"])
|
||
app.include_router(analytics.router, prefix=f"{settings.API_V1_STR}/analytics", tags=["analytics"])
|
||
app.include_router(jsx_demo.router, prefix=f"{settings.API_V1_STR}/jsx_demo", tags=["jsx_demo"])
|
||
|
||
# ❌ 删除所有静态文件挂载
|
||
# app.mount("/download", ...) # 删除
|
||
# app.mount("/shell", ...) # 删除
|
||
# app.mount("/core", ...) # 删除
|
||
|
||
@app.get("/")
|
||
def read_root():
|
||
return {"message": "DesignerCEP API Server", "version": "1.0.0"}
|
||
|
||
@app.get("/health")
|
||
def health_check():
|
||
return {"status": "healthy"}
|
||
|
||
@app.on_event("startup")
|
||
def on_startup():
|
||
init_db()
|
||
|
||
if __name__ == "__main__":
|
||
import uvicorn
|
||
uvicorn.run("app.main:app", host="127.0.0.1", port=8000, reload=True)
|
||
```
|
||
|
||
#### 2. 配置 Nginx(完整版)
|
||
|
||
```nginx
|
||
# /etc/nginx/sites-available/designer-cep
|
||
|
||
# ========== 上游 FastAPI 服务器 ==========
|
||
upstream fastapi_backend {
|
||
server 127.0.0.1:8000;
|
||
}
|
||
|
||
# ========== HTTPS 服务器 ==========
|
||
server {
|
||
listen 443 ssl http2;
|
||
server_name your-domain.com www.your-domain.com;
|
||
|
||
# SSL 证书(Cloudflare Origin Certificate)
|
||
ssl_certificate /etc/nginx/ssl/cloudflare-origin.pem;
|
||
ssl_certificate_key /etc/nginx/ssl/cloudflare-origin.key;
|
||
ssl_protocols TLSv1.2 TLSv1.3;
|
||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||
ssl_prefer_server_ciphers on;
|
||
|
||
# Cloudflare Real IP
|
||
set_real_ip_from 173.245.48.0/20;
|
||
set_real_ip_from 103.21.244.0/22;
|
||
set_real_ip_from 103.22.200.0/22;
|
||
set_real_ip_from 103.31.4.0/22;
|
||
set_real_ip_from 141.101.64.0/18;
|
||
set_real_ip_from 108.162.192.0/18;
|
||
set_real_ip_from 190.93.240.0/20;
|
||
set_real_ip_from 188.114.96.0/20;
|
||
set_real_ip_from 197.234.240.0/22;
|
||
set_real_ip_from 198.41.128.0/17;
|
||
set_real_ip_from 162.158.0.0/15;
|
||
set_real_ip_from 104.16.0.0/13;
|
||
set_real_ip_from 104.24.0.0/14;
|
||
set_real_ip_from 172.64.0.0/13;
|
||
set_real_ip_from 131.0.72.0/22;
|
||
real_ip_header CF-Connecting-IP;
|
||
|
||
# 日志
|
||
access_log /var/log/nginx/designer-cep-access.log;
|
||
error_log /var/log/nginx/designer-cep-error.log;
|
||
|
||
# ========== API 请求(转发到 FastAPI)==========
|
||
location /api/ {
|
||
proxy_pass http://fastapi_backend;
|
||
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;
|
||
|
||
# 超时设置
|
||
proxy_connect_timeout 60s;
|
||
proxy_send_timeout 60s;
|
||
proxy_read_timeout 60s;
|
||
|
||
# 禁用缓存(API 不应该缓存)
|
||
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||
add_header Pragma "no-cache";
|
||
add_header Expires "0";
|
||
}
|
||
|
||
# ========== 静态文件:Shell(在线登录页)==========
|
||
location /shell/ {
|
||
alias /var/www/DesignerCEP/Server/static/shell/;
|
||
try_files $uri $uri/ /shell/index.html;
|
||
|
||
# HTML 文件:不缓存(保证拿到最新版本)
|
||
location ~ \.html$ {
|
||
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||
add_header Pragma "no-cache";
|
||
add_header Expires "0";
|
||
}
|
||
|
||
# JS/CSS:长期缓存(文件名带 hash)
|
||
location ~* \.(js|css)$ {
|
||
expires 1y;
|
||
add_header Cache-Control "public, immutable";
|
||
}
|
||
|
||
# 图片/字体:长期缓存
|
||
location ~* \.(png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||
expires 1y;
|
||
add_header Cache-Control "public, immutable";
|
||
}
|
||
}
|
||
|
||
# ========== 静态文件:Core(核心应用)==========
|
||
location /core/ {
|
||
alias /var/www/DesignerCEP/Server/static/core/;
|
||
try_files $uri $uri/ =404;
|
||
|
||
# HTML 文件:不缓存
|
||
location ~ \.html$ {
|
||
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||
}
|
||
|
||
# JS/CSS:长期缓存
|
||
location ~* \.(js|css)$ {
|
||
expires 1y;
|
||
add_header Cache-Control "public, immutable";
|
||
}
|
||
|
||
# 图片/字体:长期缓存
|
||
location ~* \.(png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||
expires 1y;
|
||
add_header Cache-Control "public, immutable";
|
||
}
|
||
}
|
||
|
||
# ========== 下载文件:Shell.zip / Core.zip ==========
|
||
location /downloads/ {
|
||
alias /var/www/DesignerCEP/Server/static/downloads/;
|
||
|
||
# 允许大文件下载
|
||
client_max_body_size 500M;
|
||
|
||
# 启用断点续传
|
||
add_header Accept-Ranges bytes;
|
||
|
||
# 缓存 ZIP 文件(1 天)
|
||
expires 1d;
|
||
add_header Cache-Control "public";
|
||
}
|
||
|
||
# ========== 根路径 ==========
|
||
location / {
|
||
return 301 /shell/;
|
||
}
|
||
|
||
# ========== Gzip 压缩 ==========
|
||
gzip on;
|
||
gzip_vary on;
|
||
gzip_proxied any;
|
||
gzip_comp_level 6;
|
||
gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml;
|
||
|
||
# ========== 安全头 ==========
|
||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||
add_header X-Content-Type-Options "nosniff" always;
|
||
add_header X-XSS-Protection "1; mode=block" always;
|
||
}
|
||
|
||
# ========== HTTP → HTTPS 重定向 ==========
|
||
server {
|
||
listen 80;
|
||
server_name your-domain.com www.your-domain.com;
|
||
return 301 https://$server_name$request_uri;
|
||
}
|
||
```
|
||
|
||
#### 3. 启用配置
|
||
|
||
```bash
|
||
# 1. 测试配置
|
||
sudo nginx -t
|
||
|
||
# 2. 重启 Nginx
|
||
sudo systemctl restart nginx
|
||
|
||
# 3. 验证
|
||
curl -I https://your-domain.com/shell/
|
||
curl -I https://your-domain.com/api/v1/health
|
||
```
|
||
|
||
---
|
||
|
||
## 📊 修正后的架构对比
|
||
|
||
### 修正前(有问题)
|
||
|
||
```
|
||
CEP 扩展
|
||
↓ Origin: file:// ❌ CORS 失败
|
||
服务器 FastAPI
|
||
↓
|
||
静态文件 + API 混在一起 ❌ 性能差
|
||
↓
|
||
Token 在 URL ❌ 泄露风险
|
||
```
|
||
|
||
### 修正后(正确)
|
||
|
||
```
|
||
CEP 扩展
|
||
↓ Origin: null ✅ CORS 中间件处理
|
||
Nginx
|
||
├─> /api/ → FastAPI(只处理 API)
|
||
├─> /shell/ → 静态文件(高性能)
|
||
├─> /core/ → 静态文件(高性能)
|
||
└─> /downloads/ → 静态文件(支持断点续传)
|
||
↓
|
||
Token 在 Header ✅ Authorization: Bearer xxx
|
||
```
|
||
|
||
---
|
||
|
||
## ✅ 总结:修正清单
|
||
|
||
### 1. 代码修改
|
||
|
||
- [ ] **Server/app/main.py**:
|
||
- [ ] 添加 CEP CORS 中间件(处理 `Origin: null`)
|
||
- [ ] 删除所有静态文件挂载
|
||
- [ ] 添加环境判断(生产/开发)
|
||
|
||
- [ ] **Designer/src/config/index.ts**:
|
||
- [ ] 生产环境 apiServer 改为 `https://your-domain.com`
|
||
- [ ] 生产环境 shellLoginUrl 改为 `https://your-domain.com/shell/#/login`
|
||
|
||
- [ ] **Designer/src/api/request.ts**:
|
||
- [ ] 确认 token 通过 `Authorization: Bearer` 传递
|
||
- [ ] 确认 401 自动跳转登录
|
||
|
||
### 2. 服务器配置
|
||
|
||
- [ ] **Cloudflare**:
|
||
- [ ] SSL/TLS 模式设为 "Full (Strict)"
|
||
- [ ] 生成 Origin Certificate
|
||
- [ ] 下载证书和私钥
|
||
|
||
- [ ] **Nginx**:
|
||
- [ ] 上传 Cloudflare 证书
|
||
- [ ] 更新 Nginx 配置(使用上面的完整配置)
|
||
- [ ] 添加 Cloudflare Real IP 配置
|
||
- [ ] 配置静态文件缓存策略
|
||
- [ ] 重启 Nginx
|
||
|
||
### 3. 文档更新
|
||
|
||
- [ ] **部署架构说明.md**:
|
||
- [ ] 删除 `file://` 的 CORS 说明
|
||
- [ ] 添加 `Origin: null` 和 `cep://` 的处理方式
|
||
- [ ] 删除 `localhost:8000` 的混淆说明
|
||
- [ ] 明确 Core 从服务器加载
|
||
- [ ] 删除 token 在 URL 的示例
|
||
|
||
- [ ] **部署前检查清单.md**:
|
||
- [ ] 删除 Certbot 步骤
|
||
- [ ] 添加 Cloudflare Origin Certificate 步骤
|
||
|
||
### 4. 环境变量
|
||
|
||
```bash
|
||
# Server/.env
|
||
ENV=production
|
||
PROJECT_NAME=DesignerCEP
|
||
API_V1_STR=/api/v1
|
||
SECRET_KEY=your-secret-key-here
|
||
DATABASE_URL=mysql://user:password@localhost:3306/designer_cep
|
||
```
|
||
|
||
---
|
||
|
||
## 🧪 测试验证
|
||
|
||
### 1. CORS 测试
|
||
|
||
```bash
|
||
# CEP 环境测试(Origin: null)
|
||
curl -X OPTIONS https://your-domain.com/api/v1/client/login \
|
||
-H "Origin: null" \
|
||
-H "Access-Control-Request-Method: POST" \
|
||
-v
|
||
|
||
# 期望输出:
|
||
# Access-Control-Allow-Origin: *
|
||
# Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
|
||
```
|
||
|
||
### 2. Token 安全测试
|
||
|
||
```bash
|
||
# 检查 Nginx 日志,不应该有 token
|
||
tail -f /var/log/nginx/designer-cep-access.log
|
||
|
||
# 期望:只有干净的 URL
|
||
# GET /core/1.0.0/#/home HTTP/2.0
|
||
# 不应该有:GET /core/1.0.0/#/home?token=xxx
|
||
```
|
||
|
||
### 3. 静态文件性能测试
|
||
|
||
```bash
|
||
# 测试缓存头
|
||
curl -I https://your-domain.com/shell/assets/index-abc123.js
|
||
|
||
# 期望输出:
|
||
# Cache-Control: public, immutable
|
||
# Expires: (一年后的日期)
|
||
|
||
# 测试 HTML(不应该缓存)
|
||
curl -I https://your-domain.com/shell/index.html
|
||
|
||
# 期望输出:
|
||
# Cache-Control: no-cache, no-store, must-revalidate
|
||
```
|
||
|
||
---
|
||
|
||
**修正完成后,你的架构就真正可上线了!** 🎉
|
||
|