From 13f28452010d6e4c9eaa4ad81a12c30254c9acf8 Mon Sep 17 00:00:00 2001 From: aaron <> Date: Mon, 27 Apr 2026 14:51:18 +0800 Subject: [PATCH] 1 --- backend/app/api/auth.py | 45 +++++- backend/app/api/paper_trading.py | 9 +- backend/app/api/system.py | 5 +- backend/app/config.py | 2 + backend/app/crypto_agent/crypto_agent.py | 17 +- backend/app/middleware/console_auth.py | 39 +++++ frontend/console.html | 195 ++++++++++++++++++++++- 7 files changed, 292 insertions(+), 20 deletions(-) create mode 100644 backend/app/middleware/console_auth.py diff --git a/backend/app/api/auth.py b/backend/app/api/auth.py index 2319bab..8a11508 100644 --- a/backend/app/api/auth.py +++ b/backend/app/api/auth.py @@ -1,7 +1,7 @@ """ 认证API """ -from fastapi import APIRouter, HTTPException, Depends, Request +from fastapi import APIRouter, HTTPException, Depends, Request, Response from app.models.auth import ( SendCodeRequest, SendCodeResponse, LoginRequest, LoginResponse, @@ -12,11 +12,54 @@ from app.services.auth_service import auth_service from app.services.jwt_service import jwt_service from app.services.db_service import db_service from app.middleware.auth_middleware import get_current_user, get_client_ip +from app.middleware.console_auth import ( + CONSOLE_AUTH_COOKIE, + create_console_access_token, + require_console_access, +) +from app.config import get_settings from app.utils.logger import logger router = APIRouter(prefix="/api/auth", tags=["认证"]) +@router.post("/console-login") +async def console_login(request: Request, response: Response): + try: + body = await request.json() + except Exception: + body = {} + + password = str(body.get("password") or "") + settings = get_settings() + if not password or password != settings.console_access_password: + raise HTTPException(status_code=403, detail="访问密码错误") + + token = create_console_access_token() + max_age = max(1, int(settings.console_access_expire_days or 30)) * 24 * 60 * 60 + response.set_cookie( + key=CONSOLE_AUTH_COOKIE, + value=token, + max_age=max_age, + httponly=True, + samesite="lax", + secure=False, + path="/", + ) + return {"success": True, "message": "总控台登录成功"} + + +@router.post("/console-logout") +async def console_logout(response: Response): + response.delete_cookie(CONSOLE_AUTH_COOKIE, path="/") + return {"success": True, "message": "已退出总控台"} + + +@router.get("/console-session") +async def get_console_session(_: dict = Depends(require_console_access)): + return {"success": True, "authenticated": True} + + @router.post("/send-code", response_model=SendCodeResponse) async def send_verification_code( request_data: SendCodeRequest, diff --git a/backend/app/api/paper_trading.py b/backend/app/api/paper_trading.py index 9213567..f36f379 100644 --- a/backend/app/api/paper_trading.py +++ b/backend/app/api/paper_trading.py @@ -1,7 +1,7 @@ """ 交易 API """ -from fastapi import APIRouter, HTTPException, Query +from fastapi import APIRouter, HTTPException, Query, Depends from typing import Optional from datetime import datetime from pydantic import BaseModel @@ -13,6 +13,7 @@ from app.services.db_service import db_service from app.services.runtime_status_service import get_runtime_status from app.utils.logger import logger from app.crypto_agent.crypto_agent import get_crypto_agent +from app.middleware.console_auth import require_console_access router = APIRouter(prefix="/api/trading", tags=["交易"]) @@ -249,7 +250,7 @@ async def get_platform_halts(): @router.get("/execution-controls") -async def get_execution_controls(): +async def get_execution_controls(_: dict = Depends(require_console_access)): """获取目标级自动交易开关状态""" try: agent = get_crypto_agent() @@ -263,7 +264,7 @@ async def get_execution_controls(): @router.post("/execution-controls") -async def set_execution_controls(request: ExecutionControlRequest): +async def set_execution_controls(request: ExecutionControlRequest, _: dict = Depends(require_console_access)): """设置目标级自动交易开关""" try: agent = get_crypto_agent() @@ -286,7 +287,7 @@ async def set_execution_controls(request: ExecutionControlRequest): @router.post("/platform-halts/resume") -async def resume_platform(request: ResumePlatformRequest): +async def resume_platform(request: ResumePlatformRequest, _: dict = Depends(require_console_access)): """手动恢复指定平台执行""" try: agent = get_crypto_agent() diff --git a/backend/app/api/system.py b/backend/app/api/system.py index 744b0a0..ddd92f2 100644 --- a/backend/app/api/system.py +++ b/backend/app/api/system.py @@ -2,10 +2,11 @@ 系统状态 API """ from datetime import datetime, timedelta -from fastapi import APIRouter, HTTPException +from fastapi import APIRouter, HTTPException, Depends from typing import Dict, Any import numpy as np from app.config import get_settings +from app.middleware.console_auth import require_console_access from app.utils.logger import logger from app.utils.system_status import get_system_monitor from app.crypto_agent.crypto_agent import get_crypto_agent @@ -339,7 +340,7 @@ async def get_agent_status(agent_id: str): @router.get("/console", response_model=Dict[str, Any]) -async def get_console_snapshot(): +async def get_console_snapshot(_: dict = Depends(require_console_access)): """ 获取总控台快照 diff --git a/backend/app/config.py b/backend/app/config.py index 6ed9903..34fb249 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -64,6 +64,8 @@ class Settings(BaseSettings): # 安全配置 secret_key: str = "change-this-secret-key-in-production" rate_limit: str = "100/minute" + console_access_password: str = "223388" # 总控台访问密码 + console_access_expire_days: int = 30 # 总控台访问会话有效期 # JWT配置 jwt_algorithm: str = "HS256" diff --git a/backend/app/crypto_agent/crypto_agent.py b/backend/app/crypto_agent/crypto_agent.py index 140319b..1b49d90 100644 --- a/backend/app/crypto_agent/crypto_agent.py +++ b/backend/app/crypto_agent/crypto_agent.py @@ -2447,20 +2447,24 @@ class CryptoAgent: if setup_type in {'trend_continuation_pullback', 'deep_pullback_continuation', 'breakout_pullback'}: if entry_type != 'limit': return False, f"{setup_type} 应使用 limit 等待回踩/反抽" + if location_tag == 'far_from_trade_zone': + return False, f"{setup_type} 当前远离有效回踩交易区" if pullback_quality != 'healthy_pullback': - return False, f"{setup_type} 缺少健康缩量回调证据" - if location_tag in {'far_from_trade_zone', 'middle_of_range'}: - return False, f"{setup_type} 当前不在有效回踩交易区" + return True, f"{setup_type} 缺少健康缩量回调证据,降级执行" + if location_tag == 'middle_of_range': + return True, f"{setup_type} 当前位于区间中部,降级执行" if setup_type == 'range_reversal': + if location_tag == 'far_from_trade_zone': + return False, "区间反转 setup 远离关键交易区" if location_tag not in {'near_range_support', 'near_range_resistance'}: - return False, "区间反转 setup 不在区间边界" + return True, "区间反转 setup 不在区间边界,降级执行" if rejection_signal not in {'bullish_rejection', 'bearish_rejection'} and entry_type == 'market': return False, "区间反转现价执行缺少明确拒绝信号" if setup_type == 'trend_reversal': if rejection_signal not in {'bullish_rejection', 'bearish_rejection'}: - return False, "趋势反转 setup 缺少拒绝/结构切换证据" + return True, "趋势反转 setup 缺少明确拒绝信号,降级执行" if exhaustion_risk in {'upside_climax', 'downside_climax'} and setup_type != 'trend_reversal': return False, "当前量价处于高潮风险,非反转 setup 不执行" @@ -3995,6 +3999,9 @@ class CryptoAgent: setup_passed, setup_reason = self._check_setup_execution_constraints(signal) if not setup_passed: return False, f"setup 过滤: {setup_reason}" + if setup_reason != "setup 约束通过": + signal["_setup_execution_warning"] = setup_reason + logger.info(f"[{platform_name}] ⚠️ setup 降级执行: {setup_reason}") current_leverage = account.get('current_total_leverage', 0) max_leverage = account.get('max_total_leverage', 10) diff --git a/backend/app/middleware/console_auth.py b/backend/app/middleware/console_auth.py new file mode 100644 index 0000000..4a0d58b --- /dev/null +++ b/backend/app/middleware/console_auth.py @@ -0,0 +1,39 @@ +from datetime import datetime, timedelta +from typing import Dict + +from fastapi import Cookie, HTTPException +from jose import JWTError, jwt + +from app.config import get_settings + + +CONSOLE_AUTH_COOKIE = "console_access_token" + + +def create_console_access_token() -> str: + settings = get_settings() + expire = datetime.utcnow() + timedelta(days=max(1, int(settings.console_access_expire_days or 30))) + payload = { + "scope": "console_access", + "exp": expire, + "iat": datetime.utcnow(), + } + return jwt.encode(payload, settings.secret_key, algorithm=settings.jwt_algorithm) + + +def verify_console_access_token(token: str) -> Dict: + settings = get_settings() + try: + payload = jwt.decode(token, settings.secret_key, algorithms=[settings.jwt_algorithm]) + except JWTError as exc: + raise HTTPException(status_code=401, detail="总控台访问已失效,请重新登录") from exc + + if payload.get("scope") != "console_access": + raise HTTPException(status_code=401, detail="总控台访问凭证无效") + return payload + + +def require_console_access(console_access_token: str | None = Cookie(default=None, alias=CONSOLE_AUTH_COOKIE)) -> Dict: + if not console_access_token: + raise HTTPException(status_code=401, detail="请先登录总控台") + return verify_console_access_token(console_access_token) diff --git a/frontend/console.html b/frontend/console.html index 62a2d62..2103c80 100644 --- a/frontend/console.html +++ b/frontend/console.html @@ -52,6 +52,70 @@ mask-image: linear-gradient(180deg, rgba(0,0,0,0.45), rgba(0,0,0,0.9)); } + .auth-overlay { + position: fixed; + inset: 0; + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; + padding: 24px; + background: rgba(3, 8, 14, 0.82); + backdrop-filter: blur(18px); + } + + .auth-overlay.hidden { + display: none; + } + + .auth-card { + width: min(420px, 100%); + padding: 28px; + border-radius: 24px; + background: rgba(9, 18, 28, 0.96); + border: 1px solid rgba(128, 169, 202, 0.24); + box-shadow: var(--shadow); + } + + .auth-card h2 { + margin: 0 0 10px; + font-size: 28px; + } + + .auth-card p { + margin: 0 0 18px; + color: var(--muted); + line-height: 1.7; + } + + .auth-field { + width: 100%; + padding: 14px 16px; + border-radius: 14px; + border: 1px solid rgba(255,255,255,0.08); + background: rgba(255,255,255,0.04); + color: var(--text); + font-size: 15px; + outline: none; + } + + .auth-field:focus { + border-color: rgba(126, 200, 255, 0.36); + } + + .auth-actions { + display: flex; + gap: 12px; + margin-top: 14px; + } + + .auth-error { + min-height: 20px; + margin-top: 12px; + color: var(--danger); + font-size: 13px; + } + .console-shell { width: min(1600px, calc(100vw - 32px)); margin: 0 auto; @@ -2466,6 +2530,19 @@
+ +