fix(cache): remove redundant filtering and avoid mutating StorageManager results

refactor3.0
Lxy 2 weeks ago
parent 2720ddb728
commit bf4f571f0e

@ -1,6 +1,7 @@
""" """
缓存服务 - SQLite 数据库操作 缓存服务 - SQLite 数据库操作
""" """
import copy
import json import json
import logging import logging
from datetime import datetime, timedelta from datetime import datetime, timedelta
@ -295,6 +296,8 @@ def save_market_data(db: Session, symbol: str, data: Dict) -> Optional[MarketDat
try: try:
if storage.save_market_data(symbol, data): if storage.save_market_data(symbol, data):
storage.save_symbol_timestamp(symbol, data_type, now) storage.save_symbol_timestamp(symbol, data_type, now)
# 注意StorageManager 命中时返回的是内存构造的代表性 MarketData
# 不代表 SQLite 持久化记录,因此没有 SQLite 自增 id。
return _representative_market_data(symbol, data, now) return _representative_market_data(symbol, data, now)
else: else:
raise Exception("save_market_data returned False") raise Exception("save_market_data returned False")
@ -391,6 +394,8 @@ def get_latest_cached(
candle_count=len(candles), candle_count=len(candles),
) )
) )
# 注意StorageManager 命中时返回的是内存构造的 MarketData 列表,
# 不代表 SQLite 持久化记录,因此没有 SQLite 自增 id。
return records return records
except Exception as e: except Exception as e:
logger.warning(f"StorageManager 最新缓存读取失败,降级到 SQLite: {e}") logger.warning(f"StorageManager 最新缓存读取失败,降级到 SQLite: {e}")
@ -431,11 +436,9 @@ def get_cached_data(
symbol, data_type, periods, end_time=end_time, max_candles=max_candles symbol, data_type, periods, end_time=end_time, max_candles=max_candles
) )
if merged and merged.get("timeframes"): if merged and merged.get("timeframes"):
for period, candles in merged["timeframes"].items(): # StorageManager 已负责 end_time / max_candles 过滤,
merged["timeframes"][period] = _filter_candles( # 这里深拷贝后返回,避免原地修改 StorageManager 内部对象。
candles, end_time, max_candles return copy.deepcopy(merged)
)
return merged
except Exception as e: except Exception as e:
logger.warning(f"StorageManager 读取失败,降级到 SQLite: {e}") logger.warning(f"StorageManager 读取失败,降级到 SQLite: {e}")

@ -246,8 +246,8 @@ class TestGetCachedData:
"AG2606", "futures", ["5min"], end_time=end_time, max_candles=50 "AG2606", "futures", ["5min"], end_time=end_time, max_candles=50
) )
def test_applies_end_time_and_max_candles_to_storage_results(self, db, storage_manager_mock): def test_does_not_refilter_storage_manager_results(self, db, storage_manager_mock):
"""StorageManager 返回的数据会按 end_time / max_candles 过滤截断""" """cache.py 不再对 StorageManager 返回的数据做二次过滤,直接原样返回"""
fetched_at = datetime.now() fetched_at = datetime.now()
def mock_get_market_data(symbol, data_type, periods, end_time=None, max_candles=100): def mock_get_market_data(symbol, data_type, periods, end_time=None, max_candles=100):
@ -280,9 +280,40 @@ class TestGetCachedData:
assert result is not None assert result is not None
candles = result["timeframes"]["5min"] candles = result["timeframes"]["5min"]
# 过滤掉 10:05 之后保留 09:50/09:55再截断为最新 1 条 # StorageManager 未做过滤cache.py 也不应再次过滤
assert len(candles) == 1 assert len(candles) == 3
assert candles[0]["datetime"] == "2026-07-04T09:55:00"
def test_does_not_mutate_storage_manager_result(self, db, storage_manager_mock):
"""get_cached_data 返回 StorageManager 结果时不会原地修改原对象。"""
fetched_at = datetime.now()
original = {
"symbol": "AG2606",
"type": "futures",
"current_price": 123.45,
"timestamp": fetched_at.isoformat(),
"fetched_at": fetched_at.isoformat(),
"timeframes": {
"5min": [
{"datetime": "2026-07-04T09:55:00", "open": 100, "close": 110}
]
},
"is_fresh": True,
}
storage_manager_mock.get_market_data.return_value = original
with patch("app.services.cache.get_storage_manager", return_value=storage_manager_mock):
from app.services import cache
result = cache.get_cached_data(db, "AG2606", "futures", ["5min"])
assert result is not None
assert result is not original
assert result["timeframes"]["5min"] == original["timeframes"]["5min"]
# 修改返回结果不应影响 StorageManager 返回的原对象
result["timeframes"]["5min"].append(
{"datetime": "2026-07-04T10:00:00", "open": 111, "close": 112}
)
assert len(original["timeframes"]["5min"]) == 1
def test_queries_all_periods_when_periods_is_none(self, db, storage_manager_mock): def test_queries_all_periods_when_periods_is_none(self, db, storage_manager_mock):
"""periods=None 时优先走 StorageManager 查询全部周期。""" """periods=None 时优先走 StorageManager 查询全部周期。"""

Loading…
Cancel
Save