23 KiB
23 KiB
🔧 DesignerCEP 架构问题修正方案
问题汇总
经过仔细检查,当前架构存在以下 5 个严重问题,会导致本地测试 OK 但上线后炸:
- ❌ CORS 配置错误:只写
file://,CEP 环境实际是Origin: null或cep:// - ❌ Token 暴露在 URL:
#/home?token=xxx会泄露到日志、分享链接 - ❌ localhost:8000 的硬编码:文档说 Core 可从
http://localhost:8000/core/...加载,但没说这个服务谁提供 - ❌ Cloudflare 证书方案冲突:用 Cloudflare 代理时不应该用 Certbot
- ❌ 静态文件服务混乱:FastAPI 和 Nginx 职责不清
问题 1: CORS 配置错误
当前问题
# 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
✅ 修正方案
# 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
当前问题
// 文档中写的跳转方式(第 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 配置:
// 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 的来源不明
当前问题
// 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 始终从服务器加载,不依赖本地服务:
// 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 扩展从本地加载),需要:
- CEP 扩展自带本地服务器:
// 在 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();
}
}
}
- 检测端口占用并动态分配:
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 证书方案冲突
当前问题
# 部署前检查清单.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
# /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. 安装证书
# 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: 静态文件服务混乱
当前问题
# 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")
# 部署架构说明.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)
# 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(完整版)
# /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. 启用配置
# 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) - 删除所有静态文件挂载
- 添加环境判断(生产/开发)
- 添加 CEP CORS 中间件(处理
-
Designer/src/config/index.ts:
- 生产环境 apiServer 改为
https://your-domain.com - 生产环境 shellLoginUrl 改为
https://your-domain.com/shell/#/login
- 生产环境 apiServer 改为
-
Designer/src/api/request.ts:
- 确认 token 通过
Authorization: Bearer传递 - 确认 401 自动跳转登录
- 确认 token 通过
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. 环境变量
# 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 测试
# 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 安全测试
# 检查 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. 静态文件性能测试
# 测试缓存头
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
修正完成后,你的架构就真正可上线了! 🎉