feat: v0.10.0 生产环境优化 — HTTPS、前端生产构建、安全加固
- feat(deploy): 前端多阶段构建 (vite build + nginx:alpine),移除 Vite 开发模式 - feat(deploy): OpenResty HTTPS 配置 (SSL + HSTS + 安全头) - fix(ws): WebSocket 路由添加 /api 前缀,修正前后端路径不匹配 - security: SSH AutoAddPolicy → WarningPolicy - security: CORS 来源环境变量化 (CORS_ORIGINS) - security: 限流器使用 X-Forwarded-For 真实客户端 IP - perf(db): 数据库连接池配置 (pool_size=20, max_overflow=40) - refactor: 移除硬编码 URL/IP (NTP、域名、微信代理),改为环境变量 - chore: 更新 .env.example 模板,补充新增配置项 - chore: 清理 .reasonix/、scripts/、guide.md 无用文件 - docs: 更新 CLAUDE.md 至 v0.10.0,补充生产架构文档 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+18
-2
@@ -16,7 +16,7 @@ CASDOOR_CLIENT_SECRET=your_client_secret
|
||||
CASDOOR_ORG_NAME=your_org
|
||||
CASDOOR_APP_NAME=h3c-onu-ms
|
||||
CASDOOR_CERTIFICATE=backend/token_jwt_key.pem
|
||||
CASDOOR_REDIRECT_URL=http://localhost:5173/callback
|
||||
CASDOOR_REDIRECT_URL=
|
||||
|
||||
# SSH配置
|
||||
SSH_TIMEOUT=30
|
||||
@@ -25,6 +25,22 @@ SSH_TIMEOUT=30
|
||||
CHECK_INTERVAL=1800
|
||||
MANUAL_COOLDOWN=300
|
||||
|
||||
# CORS & 前端
|
||||
CORS_ORIGINS=http://localhost:5173,http://localhost:18002
|
||||
FRONTEND_URL=https://your-domain.com
|
||||
|
||||
# NTP 同步
|
||||
NTP_OLD_SERVER=172.16.0.254
|
||||
NTP_NEW_SERVER=172.16.1.252
|
||||
|
||||
# iMC API 配置(用于 ONU 远程重启和光功率查询)
|
||||
IMC_API_URL=
|
||||
IMC_API_USERNAME=
|
||||
IMC_API_PASSWORD=
|
||||
IMC_API_VERIFY_SSL=false
|
||||
IMC_CONNECT_TIMEOUT=5
|
||||
IMC_READ_TIMEOUT=20
|
||||
|
||||
# 企业微信告警配置
|
||||
WECHAT_CORPID=
|
||||
WECHAT_CORPSECRET=
|
||||
@@ -32,4 +48,4 @@ WECHAT_AGENTID=
|
||||
WECHAT_TOKEN=
|
||||
WECHAT_ENCODING_AES_KEY=
|
||||
WECHAT_USE_PROXY=True
|
||||
WECHAT_PROXY_API_URL=https://api.v6ole.top
|
||||
WECHAT_PROXY_API_URL=
|
||||
|
||||
@@ -4,6 +4,7 @@ from sqlalchemy.orm import Session
|
||||
from sqlalchemy import distinct
|
||||
from pydantic import BaseModel
|
||||
from app.core.database import get_db
|
||||
from app.core.config import settings
|
||||
from app.middleware.permission_middleware import require_permission
|
||||
from app.models.device import OLTDevice
|
||||
import pandas as pd
|
||||
@@ -501,8 +502,8 @@ def loopback_detection(
|
||||
|
||||
|
||||
class SyncNTPRequest(BaseModel):
|
||||
old_server: str = "172.16.0.254"
|
||||
new_server: str = "172.16.1.252"
|
||||
old_server: str = settings.NTP_OLD_SERVER
|
||||
new_server: str = settings.NTP_NEW_SERVER
|
||||
|
||||
|
||||
@router.post("/sync-ntp")
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import logging
|
||||
from fastapi import APIRouter, Request, Response
|
||||
from app.services.wechat_service import get_wechat_service
|
||||
from app.core.config import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter(prefix="/api/wechat", tags=["企业微信回调"])
|
||||
@@ -235,6 +236,6 @@ def _handle_help_cmd(svc, from_user: str):
|
||||
"• 发送「全离线」查看全离线学校\n"
|
||||
"• 发送 MAC 地址后四位查询设备\n\n"
|
||||
"💡 发送「帮助」显示此信息\n"
|
||||
"💻 完整功能: https://onu.dhdx.fun",
|
||||
f"💻 完整功能: {settings.FRONTEND_URL}",
|
||||
to_user=from_user
|
||||
)
|
||||
|
||||
@@ -7,7 +7,7 @@ from fastapi import APIRouter, WebSocket, WebSocketDisconnect
|
||||
from app.core.config import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter()
|
||||
router = APIRouter(prefix="/api", tags=["WebSocket"])
|
||||
|
||||
REDIS_CHANNEL = "h3c_onu:status_updates"
|
||||
_connected: set[WebSocket] = set()
|
||||
|
||||
@@ -21,12 +21,20 @@ class Settings(BaseSettings):
|
||||
CASDOOR_ORG_NAME: str
|
||||
CASDOOR_APP_NAME: str
|
||||
CASDOOR_CERTIFICATE: str = "" # 支持文件路径或直接填 PEM 内容
|
||||
CASDOOR_REDIRECT_URL: str = "http://localhost:5173/callback"
|
||||
CASDOOR_REDIRECT_URL: str = ""
|
||||
|
||||
SSH_TIMEOUT: int = 30
|
||||
CHECK_INTERVAL: int = 1800
|
||||
MANUAL_COOLDOWN: int = 300
|
||||
|
||||
# CORS & 前端
|
||||
CORS_ORIGINS: str = "" # 逗号分隔
|
||||
FRONTEND_URL: str = "https://onu.dhdx.fun"
|
||||
|
||||
# NTP 同步配置
|
||||
NTP_OLD_SERVER: str = "172.16.0.254"
|
||||
NTP_NEW_SERVER: str = "172.16.1.252"
|
||||
|
||||
# iMC API 配置(用于 ONU 远程重启和光功率查询)
|
||||
IMC_API_URL: str = ""
|
||||
|
||||
@@ -37,7 +45,7 @@ class Settings(BaseSettings):
|
||||
WECHAT_TOKEN: str = ""
|
||||
WECHAT_ENCODING_AES_KEY: str = ""
|
||||
WECHAT_USE_PROXY: bool = True
|
||||
WECHAT_PROXY_API_URL: str = "https://api.v6ole.top"
|
||||
WECHAT_PROXY_API_URL: str = ""
|
||||
IMC_API_USERNAME: str = ""
|
||||
IMC_API_PASSWORD: str = ""
|
||||
IMC_API_VERIFY_SSL: bool = False
|
||||
|
||||
@@ -4,7 +4,14 @@ from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from app.core.config import settings
|
||||
|
||||
engine = create_engine(settings.DATABASE_URL, pool_pre_ping=True)
|
||||
engine = create_engine(
|
||||
settings.DATABASE_URL,
|
||||
pool_pre_ping=True,
|
||||
pool_size=20,
|
||||
max_overflow=40,
|
||||
pool_recycle=3600,
|
||||
pool_timeout=30,
|
||||
)
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
+15
-10
@@ -1,10 +1,10 @@
|
||||
"""FastAPI 主应用"""
|
||||
import os
|
||||
import logging
|
||||
from pythonjsonlogger import jsonlogger
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from slowapi import Limiter, _rate_limit_exceeded_handler
|
||||
from slowapi.util import get_remote_address
|
||||
from slowapi.errors import RateLimitExceeded
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
from starlette.responses import JSONResponse
|
||||
@@ -29,15 +29,20 @@ class RequestSizeLimitMiddleware(BaseHTTPMiddleware):
|
||||
return JSONResponse({"detail": "请求体过大,最大 10MB"}, status_code=413)
|
||||
return await call_next(request)
|
||||
|
||||
# CORS 白名单
|
||||
ALLOWED_ORIGINS = [
|
||||
"http://localhost:5173",
|
||||
"http://localhost:18002",
|
||||
"https://onu.dhdx.fun",
|
||||
]
|
||||
allowed = [o for o in ALLOWED_ORIGINS if o]
|
||||
# CORS 白名单 — 支持通过环境变量 CORS_ORIGINS 覆盖(逗号分隔)
|
||||
CORS_ORIGINS_DEFAULT = "http://localhost:5173,http://localhost:18002,https://onu.dhdx.fun"
|
||||
ALLOWED_ORIGINS = [o.strip() for o in os.getenv("CORS_ORIGINS", CORS_ORIGINS_DEFAULT).split(",") if o.strip()]
|
||||
|
||||
limiter = Limiter(key_func=get_remote_address, default_limits=["120/minute"])
|
||||
|
||||
def get_client_ip(request: Request) -> str:
|
||||
"""读取 X-Forwarded-For 首字段作为真实客户端 IP"""
|
||||
forwarded = request.headers.get("X-Forwarded-For")
|
||||
if forwarded:
|
||||
return forwarded.split(",")[0].strip()
|
||||
return request.client.host if request.client else "unknown"
|
||||
|
||||
|
||||
limiter = Limiter(key_func=get_client_ip, default_limits=["120/minute"])
|
||||
|
||||
app = FastAPI(title=settings.APP_NAME, debug=settings.DEBUG)
|
||||
app.state.limiter = limiter
|
||||
@@ -46,7 +51,7 @@ app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
|
||||
app.add_middleware(RequestSizeLimitMiddleware)
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=allowed if allowed else ["*"],
|
||||
allow_origins=ALLOWED_ORIGINS if ALLOWED_ORIGINS else ["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
|
||||
@@ -36,7 +36,7 @@ class SSHService:
|
||||
"""建立 SSH 连接,等待初始 banner 输出完毕"""
|
||||
try:
|
||||
self.client = paramiko.SSHClient()
|
||||
self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
self.client.set_missing_host_key_policy(paramiko.WarningPolicy())
|
||||
self.client.connect(
|
||||
hostname=self.host,
|
||||
port=self.port,
|
||||
|
||||
Reference in New Issue
Block a user