# 🔧 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 { 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 ``` --- **修正完成后,你的架构就真正可上线了!** 🎉