This commit is contained in:
薇薇安 2026-02-17 23:13:49 +08:00
parent b0392f358e
commit c7f1361d99
3 changed files with 389 additions and 237 deletions

103
backend/TROUBLESHOOTING.md Normal file
View File

@ -0,0 +1,103 @@
# Backend 启动问题排查指南
## 常见问题
### 1. 语法错误
**错误信息**: `SyntaxError: expected 'except' or 'finally' block`
**解决方法**:
- 检查代码中的 `try:` 块是否都有对应的 `except``finally`
- 检查缩进是否正确
- 运行 `python3 -m py_compile api/routes/trades.py` 检查语法
### 2. 缺少依赖模块
**错误信息**: `ModuleNotFoundError: No module named 'xxx'`
**解决方法**:
```bash
# 激活虚拟环境
source ../.venv/bin/activate # 或 source .venv/bin/activate
# 安装依赖
pip install -r requirements.txt
# 或者单独安装缺失的模块
pip install python-jose[cryptography]
```
### 3. 导入错误
**错误信息**: `ModuleNotFoundError: No module named 'api'`
**解决方法**:
- 确保在 `backend` 目录下运行
- 检查 `PYTHONPATH` 是否正确设置
- 使用 `cd backend && python3 -m uvicorn api.main:app` 启动
## 排查步骤
### 步骤 1: 检查依赖
```bash
cd backend
bash check_dependencies.sh
```
### 步骤 2: 检查语法
```bash
cd backend
source ../.venv/bin/activate
python3 -m py_compile api/main.py
python3 -m py_compile api/routes/*.py
```
### 步骤 3: 测试导入
```bash
cd backend
source ../.venv/bin/activate
python3 -c "import api.main; print('✓ 导入成功')"
```
### 步骤 4: 查看日志
```bash
# 查看最新的错误日志
tail -50 backend/logs/api.log
tail -50 backend/logs/uvicorn.log
```
### 步骤 5: 手动启动测试
```bash
cd backend
source ../.venv/bin/activate
export DB_HOST=your_db_host
export DB_PORT=3306
export DB_USER=your_db_user
export DB_PASSWORD=your_db_password
export DB_NAME=auto_trade_sys
uvicorn api.main:app --host 0.0.0.0 --port 8001 --log-level info
```
## 启动脚本
### 开发模式(自动重载)
```bash
cd backend
./start_dev.sh
```
### 生产模式(后台运行)
```bash
cd backend
./start.sh
```
## 检查服务状态
```bash
# 检查进程
ps aux | grep uvicorn
# 检查端口
lsof -i :8001
# 测试健康检查
curl http://localhost:8001/api/health
```

View File

@ -588,167 +588,170 @@ async def sync_trades_from_binance(
try:
# 这是平仓订单close_orders 已经筛选出 reduceOnly=True 的订单)
# 首先检查是否已经通过订单号同步过(避免重复)
existing_trade = Trade.get_by_exit_order_id(order_id)
# 如果已有 exit_order_id 且 exit_reason 不是 sync说明已完整同步跳过
if existing_trade and existing_trade.get("exit_order_id") and existing_trade.get("exit_reason") not in (None, "", "sync"):
skipped_existing += 1
logger.debug(f"订单 {order_id} 已完整同步过exit_reason={existing_trade.get('exit_reason')}),跳过")
continue
# 查找数据库中该交易对的 open 状态记录(仅当前账号),或已平仓但 exit_order_id 为空的记录
open_trades = Trade.get_by_symbol(symbol, status='open', account_id=account_id or DEFAULT_ACCOUNT_ID)
closed_trades_no_exit_id = []
if not existing_trade and not open_trades:
# 如果没有 open 记录,查找已平仓但 exit_order_id 为空的记录
try:
closed_trades = Trade.get_by_symbol(symbol, status='closed', account_id=account_id or DEFAULT_ACCOUNT_ID)
closed_trades_no_exit_id = [
t for t in closed_trades
if not t.get("exit_order_id") or str(t.get("exit_order_id")).strip() in ("", "0")
]
except Exception:
pass
if existing_trade or open_trades or closed_trades_no_exit_id:
# 找到匹配的交易记录(优先用 existing_trade其次 open_trades最后 closed_trades_no_exit_id
if existing_trade:
trade = existing_trade
elif open_trades:
trade = open_trades[0] # 取第一个 open 记录
else:
# 从已平仓但无 exit_order_id 的记录中选择(按 entry_time 最近的一条)
closed_trades_no_exit_id.sort(key=lambda x: x.get('entry_time', 0) or 0, reverse=True)
trade = closed_trades_no_exit_id[0]
trade_id = trade['id']
# 如果之前没有 exit_order_id记录为补全
if not trade.get("exit_order_id") or str(trade.get("exit_order_id")).strip() in ("", "0"):
exit_order_id_filled += 1
# 计算盈亏
entry_price = float(trade['entry_price'])
entry_quantity = float(trade['quantity'])
# 使用实际成交数量(可能部分平仓)
actual_quantity = min(quantity, entry_quantity)
if trade['side'] == 'BUY':
pnl = (avg_price - entry_price) * actual_quantity
pnl_percent = ((avg_price - entry_price) / entry_price) * 100
else: # SELL
pnl = (entry_price - avg_price) * actual_quantity
pnl_percent = ((entry_price - avg_price) / entry_price) * 100
# 细分 exit_reason优先使用币安订单类型其次用价格接近止损/止盈做兜底
exit_reason = "sync"
# 检查订单的 reduceOnly 字段:如果是 true说明是自动平仓不应该标记为 manual
is_reduce_only = order.get("reduceOnly", False) if isinstance(order, dict) else False
if "TRAILING" in otype:
exit_reason = "trailing_stop"
elif "TAKE_PROFIT" in otype:
exit_reason = "take_profit"
elif "STOP" in otype:
exit_reason = "stop_loss"
elif otype in ("MARKET", "LIMIT"):
# 如果是 reduceOnly 订单,说明是自动平仓(可能是保护单触发的),先标记为 sync后续用价格判断
if is_reduce_only:
exit_reason = "sync" # 临时标记,后续用价格判断
else:
exit_reason = "manual" # 非 reduceOnly 的 MARKET/LIMIT 订单才是真正的手动平仓
try:
def _close_to(a: float, b: float, max_pct: float = 0.02) -> bool: # 放宽到2%,因为滑点可能导致价格不完全一致
if a <= 0 or b <= 0:
return False
return abs((a - b) / b) <= max_pct
ep = float(avg_price or 0)
if ep > 0:
sl = trade.get("stop_loss_price")
tp = trade.get("take_profit_price")
tp1 = trade.get("take_profit_1")
tp2 = trade.get("take_profit_2")
# 优先检查止损
if sl is not None and _close_to(ep, float(sl), max_pct=0.02):
exit_reason = "stop_loss"
# 然后检查止盈
elif tp is not None and _close_to(ep, float(tp), max_pct=0.02):
exit_reason = "take_profit"
elif tp1 is not None and _close_to(ep, float(tp1), max_pct=0.02):
exit_reason = "take_profit"
elif tp2 is not None and _close_to(ep, float(tp2), max_pct=0.02):
exit_reason = "take_profit"
# 如果价格接近入场价,可能是移动止损触发的
elif is_reduce_only and exit_reason == "sync":
entry_price_val = float(trade.get("entry_price", 0) or 0)
if entry_price_val > 0 and _close_to(ep, entry_price_val, max_pct=0.01):
exit_reason = "trailing_stop"
except Exception:
pass
# 从币安成交获取手续费与实际盈亏,保证统计与币安一致
sync_commission = None
sync_commission_asset = None
sync_realized_pnl = None
try:
recent_trades = await client.get_recent_trades(symbol, limit=30)
related = [t for t in recent_trades if str(t.get('orderId')) == str(order_id)]
if related:
sync_commission = sum(float(t.get('commission', 0)) for t in related)
assets = {t.get('commissionAsset') for t in related if t.get('commissionAsset')}
sync_commission_asset = "/".join(assets) if assets else None
sync_realized_pnl = sum(float(t.get('realizedPnl', 0)) for t in related)
except Exception as fee_err:
logger.debug(f"同步订单 {order_id} 手续费失败: {fee_err}")
# 持仓持续时间(分钟)
duration_minutes = None
try:
et = trade.get("entry_time")
if et is not None and exit_time_ts is not None:
et_i = int(et)
if exit_time_ts >= et_i:
duration_minutes = int((exit_time_ts - et_i) / 60)
except Exception:
duration_minutes = None
# 更新数据库(包含订单号、手续费与实际盈亏)
Trade.update_exit(
trade_id=trade_id,
exit_price=avg_price,
exit_reason=exit_reason,
pnl=pnl,
pnl_percent=pnl_percent,
exit_order_id=order_id, # 保存订单号,确保唯一性
duration_minutes=duration_minutes,
exit_time_ts=exit_time_ts,
commission=sync_commission,
commission_asset=sync_commission_asset,
realized_pnl=sync_realized_pnl,
)
updated_count += 1
logger.info(
f"✓ 更新平仓记录: {symbol} (ID: {trade_id}, 订单号: {order_id}, "
f"类型: {otype or '-'}, 原因: {exit_reason}, 成交价: {avg_price:.4f})"
)
# 首先检查是否已经通过订单号同步过(避免重复)
existing_trade = Trade.get_by_exit_order_id(order_id)
# 如果已有 exit_order_id 且 exit_reason 不是 sync说明已完整同步跳过
if existing_trade and existing_trade.get("exit_order_id") and existing_trade.get("exit_reason") not in (None, "", "sync"):
skipped_existing += 1
logger.debug(f"订单 {order_id} 已完整同步过exit_reason={existing_trade.get('exit_reason')}),跳过")
continue
# 查找数据库中该交易对的 open 状态记录(仅当前账号),或已平仓但 exit_order_id 为空的记录
open_trades = Trade.get_by_symbol(symbol, status='open', account_id=account_id or DEFAULT_ACCOUNT_ID)
closed_trades_no_exit_id = []
if not existing_trade and not open_trades:
# 如果没有 open 记录,查找已平仓但 exit_order_id 为空的记录
try:
closed_trades = Trade.get_by_symbol(symbol, status='closed', account_id=account_id or DEFAULT_ACCOUNT_ID)
closed_trades_no_exit_id = [
t for t in closed_trades
if not t.get("exit_order_id") or str(t.get("exit_order_id")).strip() in ("", "0")
]
except Exception:
pass
if existing_trade or open_trades or closed_trades_no_exit_id:
# 找到匹配的交易记录(优先用 existing_trade其次 open_trades最后 closed_trades_no_exit_id
if existing_trade:
trade = existing_trade
elif open_trades:
trade = open_trades[0] # 取第一个 open 记录
else:
# 没有找到匹配的记录
if sync_all_symbols:
# 如果启用了同步所有交易对,尝试创建完整的交易记录(开仓+平仓)
try:
# 查找是否有对应的开仓订单(通过时间窗口和价格匹配)
# 注意:平仓订单通常有 reduceOnly=True我们需要找到对应的开仓订单
# 由于币安订单历史可能不完整,这里先跳过创建,只记录日志
logger.debug(f"平仓订单 {order_id} ({symbol}) 无法匹配到现有记录,且 sync_all_symbols=True但创建完整交易记录需要开仓订单信息暂时跳过")
skipped_no_match += 1
except Exception as e:
logger.debug(f"处理平仓订单失败 {order_id}: {e}")
skipped_no_match += 1
# 从已平仓但无 exit_order_id 的记录中选择(按 entry_time 最近的一条)
closed_trades_no_exit_id.sort(key=lambda x: x.get('entry_time', 0) or 0, reverse=True)
trade = closed_trades_no_exit_id[0]
trade_id = trade['id']
# 如果之前没有 exit_order_id记录为补全
if not trade.get("exit_order_id") or str(trade.get("exit_order_id")).strip() in ("", "0"):
exit_order_id_filled += 1
# 计算盈亏
entry_price = float(trade['entry_price'])
entry_quantity = float(trade['quantity'])
# 使用实际成交数量(可能部分平仓)
actual_quantity = min(quantity, entry_quantity)
if trade['side'] == 'BUY':
pnl = (avg_price - entry_price) * actual_quantity
pnl_percent = ((avg_price - entry_price) / entry_price) * 100
else: # SELL
pnl = (entry_price - avg_price) * actual_quantity
pnl_percent = ((entry_price - avg_price) / entry_price) * 100
# 细分 exit_reason优先使用币安订单类型其次用价格接近止损/止盈做兜底
exit_reason = "sync"
# 检查订单的 reduceOnly 字段:如果是 true说明是自动平仓不应该标记为 manual
is_reduce_only = order.get("reduceOnly", False) if isinstance(order, dict) else False
if "TRAILING" in otype:
exit_reason = "trailing_stop"
elif "TAKE_PROFIT" in otype:
exit_reason = "take_profit"
elif "STOP" in otype:
exit_reason = "stop_loss"
elif otype in ("MARKET", "LIMIT"):
# 如果是 reduceOnly 订单,说明是自动平仓(可能是保护单触发的),先标记为 sync后续用价格判断
if is_reduce_only:
exit_reason = "sync" # 临时标记,后续用价格判断
else:
exit_reason = "manual" # 非 reduceOnly 的 MARKET/LIMIT 订单才是真正的手动平仓
try:
def _close_to(a: float, b: float, max_pct: float = 0.02) -> bool: # 放宽到2%,因为滑点可能导致价格不完全一致
if a <= 0 or b <= 0:
return False
return abs((a - b) / b) <= max_pct
ep = float(avg_price or 0)
if ep > 0:
sl = trade.get("stop_loss_price")
tp = trade.get("take_profit_price")
tp1 = trade.get("take_profit_1")
tp2 = trade.get("take_profit_2")
# 优先检查止损
if sl is not None and _close_to(ep, float(sl), max_pct=0.02):
exit_reason = "stop_loss"
# 然后检查止盈
elif tp is not None and _close_to(ep, float(tp), max_pct=0.02):
exit_reason = "take_profit"
elif tp1 is not None and _close_to(ep, float(tp1), max_pct=0.02):
exit_reason = "take_profit"
elif tp2 is not None and _close_to(ep, float(tp2), max_pct=0.02):
exit_reason = "take_profit"
# 如果价格接近入场价,可能是移动止损触发的
elif is_reduce_only and exit_reason == "sync":
entry_price_val = float(trade.get("entry_price", 0) or 0)
if entry_price_val > 0 and _close_to(ep, entry_price_val, max_pct=0.01):
exit_reason = "trailing_stop"
except Exception:
pass
# 从币安成交获取手续费与实际盈亏,保证统计与币安一致
sync_commission = None
sync_commission_asset = None
sync_realized_pnl = None
try:
recent_trades = await client.get_recent_trades(symbol, limit=30)
related = [t for t in recent_trades if str(t.get('orderId')) == str(order_id)]
if related:
sync_commission = sum(float(t.get('commission', 0)) for t in related)
assets = {t.get('commissionAsset') for t in related if t.get('commissionAsset')}
sync_commission_asset = "/".join(assets) if assets else None
sync_realized_pnl = sum(float(t.get('realizedPnl', 0)) for t in related)
except Exception as fee_err:
logger.debug(f"同步订单 {order_id} 手续费失败: {fee_err}")
# 持仓持续时间(分钟)
duration_minutes = None
try:
et = trade.get("entry_time")
if et is not None and exit_time_ts is not None:
et_i = int(et)
if exit_time_ts >= et_i:
duration_minutes = int((exit_time_ts - et_i) / 60)
except Exception:
duration_minutes = None
# 更新数据库(包含订单号、手续费与实际盈亏)
Trade.update_exit(
trade_id=trade_id,
exit_price=avg_price,
exit_reason=exit_reason,
pnl=pnl,
pnl_percent=pnl_percent,
exit_order_id=order_id, # 保存订单号,确保唯一性
duration_minutes=duration_minutes,
exit_time_ts=exit_time_ts,
commission=sync_commission,
commission_asset=sync_commission_asset,
realized_pnl=sync_realized_pnl,
)
updated_count += 1
logger.info(
f"✓ 更新平仓记录: {symbol} (ID: {trade_id}, 订单号: {order_id}, "
f"类型: {otype or '-'}, 原因: {exit_reason}, 成交价: {avg_price:.4f})"
)
else:
# 没有找到匹配的记录
if sync_all_symbols:
# 如果启用了同步所有交易对,尝试创建完整的交易记录(开仓+平仓)
try:
# 查找是否有对应的开仓订单(通过时间窗口和价格匹配)
# 注意:平仓订单通常有 reduceOnly=True我们需要找到对应的开仓订单
# 由于币安订单历史可能不完整,这里先跳过创建,只记录日志
logger.debug(f"平仓订单 {order_id} ({symbol}) 无法匹配到现有记录,且 sync_all_symbols=True但创建完整交易记录需要开仓订单信息暂时跳过")
skipped_no_match += 1
logger.debug(f"平仓订单 {order_id} ({symbol}) 无法匹配到现有记录(无 open 状态且无 exit_order_id 为空的 closed 记录),跳过")
except Exception as e:
logger.debug(f"处理平仓订单失败 {order_id}: {e}")
skipped_no_match += 1
else:
skipped_no_match += 1
logger.debug(f"平仓订单 {order_id} ({symbol}) 无法匹配到现有记录(无 open 状态且无 exit_order_id 为空的 closed 记录),跳过")
except Exception as e:
logger.warning(f"同步平仓订单失败 {symbol} (订单ID: {order_id}): {e}")
continue
# 2. 处理开仓订单
logger.info(f"开始处理 {len(open_orders)} 个开仓订单...")
for order in open_orders:
@ -772,86 +775,86 @@ async def sync_trades_from_binance(
else:
# 如果不存在,尝试查找没有 entry_order_id 的记录并补全,或创建新记录
try:
# 查找该 symbol 下没有 entry_order_id 的记录(按时间匹配)
order_time_ms = order.get('time', 0)
order_time_sec = order_time_ms // 1000 if order_time_ms > 0 else 0
# 查找时间范围内(订单时间前后 1 小时)且没有 entry_order_id 的记录
time_window_start = order_time_sec - 3600
time_window_end = order_time_sec + 3600
trades_no_entry_id = Trade.get_all(
account_id=account_id or DEFAULT_ACCOUNT_ID,
symbol=symbol,
start_timestamp=time_window_start,
end_timestamp=time_window_end,
time_filter="entry", # 使用 entry_time 过滤
)
# 过滤出没有 entry_order_id 的记录
trades_no_entry_id = [
t for t in trades_no_entry_id
if not t.get("entry_order_id") or str(t.get("entry_order_id")).strip() in ("", "0")
]
# 按价格和数量匹配(允许 5% 误差)
matched_trade = None
order_qty = float(order.get('executedQty', 0))
order_price = float(order.get('avgPrice', 0))
order_side = order.get('side', '').upper()
for t in trades_no_entry_id:
t_qty = float(t.get('quantity', 0))
t_price = float(t.get('entry_price', 0))
# 数量匹配(允许 5% 误差)且价格匹配(允许 2% 误差)
if (order_qty > 0 and t_qty > 0 and
abs(t_qty - order_qty) / max(order_qty, 1e-8) <= 0.05 and
order_price > 0 and t_price > 0 and
abs(t_price - order_price) / max(order_price, 1e-8) <= 0.02):
matched_trade = t
break
if matched_trade:
# 补全 entry_order_id
from database.models import Trade as TradeModel
if TradeModel.update_entry_order_id(matched_trade['id'], order_id):
entry_order_id_filled += 1
logger.info(f"✓ 补全开仓订单号: {symbol} (ID: {matched_trade['id']}, orderId: {order_id}, qty={order_qty}, price={order_price:.4f})")
else:
logger.debug(f"补全开仓订单号失败(可能已有订单号): {symbol} (ID: {matched_trade['id']}, orderId: {order_id})")
elif sync_all_symbols:
# 如果启用了同步所有交易对,且无法匹配到现有记录,创建新记录
try:
# 从订单信息中提取杠杆(如果有)
leverage = 10 # 默认杠杆
try:
# 尝试从订单的 positionSide 或其他字段获取杠杆信息
# 如果没有,使用默认值
pass
except:
pass
# 创建新的交易记录
trade_id = Trade.create(
symbol=symbol,
side=order_side,
quantity=order_qty,
entry_price=order_price,
leverage=leverage,
entry_reason='sync_from_binance',
entry_order_id=order_id,
client_order_id=order.get('clientOrderId'),
account_id=account_id or DEFAULT_ACCOUNT_ID,
status='open', # 先标记为 open如果后续有平仓订单会更新
)
created_count += 1
logger.info(f"✓ 创建新交易记录: {symbol} (ID: {trade_id}, orderId: {order_id}, side={order_side}, qty={order_qty}, price={order_price:.4f})")
except Exception as create_err:
logger.warning(f"创建交易记录失败 {symbol} (orderId: {order_id}): {create_err}")
# 查找该 symbol 下没有 entry_order_id 的记录(按时间匹配)
order_time_ms = order.get('time', 0)
order_time_sec = order_time_ms // 1000 if order_time_ms > 0 else 0
# 查找时间范围内(订单时间前后 1 小时)且没有 entry_order_id 的记录
time_window_start = order_time_sec - 3600
time_window_end = order_time_sec + 3600
trades_no_entry_id = Trade.get_all(
account_id=account_id or DEFAULT_ACCOUNT_ID,
symbol=symbol,
start_timestamp=time_window_start,
end_timestamp=time_window_end,
time_filter="entry", # 使用 entry_time 过滤
)
# 过滤出没有 entry_order_id 的记录
trades_no_entry_id = [
t for t in trades_no_entry_id
if not t.get("entry_order_id") or str(t.get("entry_order_id")).strip() in ("", "0")
]
# 按价格和数量匹配(允许 5% 误差)
matched_trade = None
order_qty = float(order.get('executedQty', 0))
order_price = float(order.get('avgPrice', 0))
order_side = order.get('side', '').upper()
for t in trades_no_entry_id:
t_qty = float(t.get('quantity', 0))
t_price = float(t.get('entry_price', 0))
# 数量匹配(允许 5% 误差)且价格匹配(允许 2% 误差)
if (order_qty > 0 and t_qty > 0 and
abs(t_qty - order_qty) / max(order_qty, 1e-8) <= 0.05 and
order_price > 0 and t_price > 0 and
abs(t_price - order_price) / max(order_price, 1e-8) <= 0.02):
matched_trade = t
break
if matched_trade:
# 补全 entry_order_id
from database.models import Trade as TradeModel
if TradeModel.update_entry_order_id(matched_trade['id'], order_id):
entry_order_id_filled += 1
logger.info(f"✓ 补全开仓订单号: {symbol} (ID: {matched_trade['id']}, orderId: {order_id}, qty={order_qty}, price={order_price:.4f})")
else:
logger.debug(f"发现新的开仓订单 {order_id} ({symbol}, qty={order_qty}, price={order_price:.4f})但无法匹配到现有记录sync_all_symbols=False跳过创建")
except Exception as e:
logger.debug(f"处理开仓订单失败 {order_id}: {e}")
logger.debug(f"补全开仓订单号失败(可能已有订单号): {symbol} (ID: {matched_trade['id']}, orderId: {order_id})")
elif sync_all_symbols:
# 如果启用了同步所有交易对,且无法匹配到现有记录,创建新记录
try:
# 从订单信息中提取杠杆(如果有)
leverage = 10 # 默认杠杆
try:
# 尝试从订单的 positionSide 或其他字段获取杠杆信息
# 如果没有,使用默认值
pass
except:
pass
# 创建新的交易记录
trade_id = Trade.create(
symbol=symbol,
side=order_side,
quantity=order_qty,
entry_price=order_price,
leverage=leverage,
entry_reason='sync_from_binance',
entry_order_id=order_id,
client_order_id=order.get('clientOrderId'),
account_id=account_id or DEFAULT_ACCOUNT_ID,
status='open', # 先标记为 open如果后续有平仓订单会更新
)
created_count += 1
logger.info(f"✓ 创建新交易记录: {symbol} (ID: {trade_id}, orderId: {order_id}, side={order_side}, qty={order_qty}, price={order_price:.4f})")
except Exception as create_err:
logger.warning(f"创建交易记录失败 {symbol} (orderId: {order_id}): {create_err}")
else:
logger.debug(f"发现新的开仓订单 {order_id} ({symbol}, qty={order_qty}, price={order_price:.4f})但无法匹配到现有记录sync_all_symbols=False跳过创建")
except Exception as e:
logger.debug(f"处理开仓订单失败 {order_id}: {e}")
except Exception as e:
logger.warning(f"同步订单失败 {symbol} (订单ID: {order_id}): {e}")
continue

46
backend/check_dependencies.sh Executable file
View File

@ -0,0 +1,46 @@
#!/bin/bash
# 检查 backend 依赖是否完整安装
cd "$(dirname "$0")"
echo "=== 检查 Backend 依赖 ==="
echo ""
# 检查虚拟环境
if [ -d "../.venv" ]; then
echo "✓ 找到虚拟环境: ../.venv"
source ../.venv/bin/activate
elif [ -d ".venv" ]; then
echo "✓ 找到虚拟环境: .venv"
source .venv/bin/activate
else
echo "⚠ 未找到虚拟环境,使用系统 Python"
fi
echo ""
echo "Python 路径: $(which python3)"
echo "Python 版本: $(python3 --version)"
echo ""
# 检查关键依赖
echo "检查关键依赖..."
python3 -c "import fastapi; print('✓ fastapi:', fastapi.__version__)" 2>&1 || echo "✗ fastapi 未安装"
python3 -c "import uvicorn; print('✓ uvicorn:', uvicorn.__version__)" 2>&1 || echo "✗ uvicorn 未安装"
python3 -c "from jose import jwt; print('✓ python-jose: 已安装')" 2>&1 || echo "✗ python-jose 未安装"
python3 -c "import pymysql; print('✓ pymysql:', pymysql.__version__)" 2>&1 || echo "✗ pymysql 未安装"
python3 -c "import redis; print('✓ redis:', redis.__version__)" 2>&1 || echo "✗ redis 未安装"
python3 -c "from cryptography.fernet import Fernet; print('✓ cryptography: 已安装')" 2>&1 || echo "✗ cryptography 未安装"
echo ""
echo "=== 尝试导入 api.main ==="
python3 -c "import api.main; print('✓ api.main 导入成功')" 2>&1 || echo "✗ api.main 导入失败"
echo ""
echo "=== 检查完成 ==="
echo ""
echo "如果缺少依赖,请运行:"
echo " pip install -r backend/requirements.txt"
echo ""
echo "或者激活虚拟环境后运行:"
echo " source .venv/bin/activate"
echo " pip install -r backend/requirements.txt"