Files
DP/tempdocs/架构问题修正方案.md

23 KiB
Raw Blame History

🔧 DesignerCEP 架构问题修正方案

问题汇总

经过仔细检查,当前架构存在以下 5 个严重问题,会导致本地测试 OK 但上线后炸:

  1. CORS 配置错误:只写 file://CEP 环境实际是 Origin: nullcep://
  2. Token 暴露在 URL#/home?token=xxx 会泄露到日志、分享链接
  3. localhost:8000 的硬编码:文档说 Core 可从 http://localhost:8000/core/... 加载,但没说这个服务谁提供
  4. Cloudflare 证书方案冲突:用 Cloudflare 代理时不应该用 Certbot
  5. 静态文件服务混乱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 是 nullcep://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: nullcep:// 情况
  • OPTIONS 预检请求会正常通过

问题 2: Token 暴露在 URL

当前问题

// 文档中写的跳转方式(第 178 行)
/core/1.0.0/#/home?token=xxx&username=xxx&device_id=xxx

// 问题:
// 1. Nginx access.log 会记录 URLtoken 泄露)
// 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
  }
};

方案 BCEP 扩展离线模式(需要额外开发)

如果要支持离线使用CEP 扩展从本地加载),需要:

  1. 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();
    }
  }
}
  1. 检测端口占用并动态分配
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
    • 删除所有静态文件挂载
    • 添加环境判断(生产/开发)
  • 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: nullcep:// 的处理方式
    • 删除 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

修正完成后,你的架构就真正可上线了! 🎉