This commit is contained in:
2026-08-18 16:13:07 +08:00
parent efcbc49096
commit 4de893ca94
12 changed files with 906 additions and 506 deletions
+49 -22
View File
@@ -1,18 +1,50 @@
import inspect import inspect
import json import json
import logging
from api.core.Gzip import gzip_zip, gzip_unzip from api.core.Gzip import gzip_zip, gzip_unzip
from config.main import config from config.main import config
logger = logging.getLogger('dcalc.redis_cache')
async def get_redis_info_async(redis, key, fun, expire = 0, without_gzip=False):
def _read_cached(redis, key, without_gzip):
content = redis.get(key)
if content is None:
return None
return json.loads(content if without_gzip else gzip_unzip(content))
def _write_cached(redis, key, info, expire, without_gzip):
content = json.dumps(info) if without_gzip else gzip_zip(json.dumps(info))
if expire > 0:
redis.set(key, content, ex=expire)
else:
redis.set(key, content)
async def _read_cached_async(redis, key, without_gzip):
content = await redis.get(key)
if content is None:
return None
return json.loads(content if without_gzip else gzip_unzip(content))
async def _write_cached_async(redis, key, info, expire, without_gzip):
content = json.dumps(info) if without_gzip else gzip_zip(json.dumps(info))
if expire > 0:
await redis.set(key, content, ex=expire)
else:
await redis.set(key, content)
async def get_redis_info_async(redis, key, fun, expire=0, without_gzip=False):
info = None info = None
if not config.DEBUG_MODE and redis: if not config.DEBUG_MODE and redis:
try: try:
if without_gzip: info = await _read_cached_async(redis, key, without_gzip)
info = json.loads(redis.get(key))
else:
info = json.loads(gzip_unzip(redis.get(key)))
except Exception: except Exception:
logger.warning('read Redis cache failed key=%s', key, exc_info=True)
info = None info = None
if info is None: if info is None:
if inspect.iscoroutinefunction(fun): if inspect.iscoroutinefunction(fun):
@@ -20,31 +52,26 @@ async def get_redis_info_async(redis, key, fun, expire = 0, without_gzip=False):
else: else:
info = fun() info = fun()
if not config.DEBUG_MODE and redis and info is not None: if not config.DEBUG_MODE and redis and info is not None:
if without_gzip: try:
redis.set(key, json.dumps(info)) await _write_cached_async(redis, key, info, expire, without_gzip)
else: except Exception:
redis.set(key, gzip_zip(json.dumps(info))) logger.warning('write Redis cache failed key=%s', key, exc_info=True)
if expire > 0:
redis.expire(key, expire)
return info return info
def get_redis_info(redis, key, fun, expire = 0, without_gzip=False):
def get_redis_info(redis, key, fun, expire=0, without_gzip=False):
info = None info = None
if not config.DEBUG_MODE and redis: if not config.DEBUG_MODE and redis:
try: try:
if without_gzip: info = _read_cached(redis, key, without_gzip)
info = json.loads(redis.get(key))
else:
info = json.loads(gzip_unzip(redis.get(key)))
except Exception: except Exception:
logger.warning('read Redis cache failed key=%s', key, exc_info=True)
info = None info = None
if info is None: if info is None:
info = fun() info = fun()
if not config.DEBUG_MODE and redis and info is not None: if not config.DEBUG_MODE and redis and info is not None:
if without_gzip: try:
redis.set(key, json.dumps(info)) _write_cached(redis, key, info, expire, without_gzip)
else: except Exception:
redis.set(key, gzip_zip(json.dumps(info))) logger.warning('write Redis cache failed key=%s', key, exc_info=True)
if expire > 0:
redis.expire(key, expire)
return info return info
+4 -3
View File
@@ -1,19 +1,20 @@
import time import time
from typing import Annotated from typing import Annotated
from redis import Redis from redis import Redis
from sqlalchemy.orm import Session from redis.asyncio import Redis as AsyncRedis
from fastapi import Depends, Header from fastapi import Depends, Header
from api.core.exception import ResponseException from api.core.exception import ResponseException
from core.character.adventure import get_adv_info from core.character.adventure import get_adv_info
from database.connect import get_db, get_redis from database.connect import get_async_redis, get_redis
from api.core.Auth import AlterState from api.core.Auth import AlterState
import base64 import base64
import json import json
from api.core.Redis import get_redis_info from api.core.Redis import get_redis_info
SessionDep = Annotated[Session, Depends(get_db)]
RedisDep = Annotated[Redis, Depends(get_redis)] RedisDep = Annotated[Redis, Depends(get_redis)]
AsyncRedisDep = Annotated[AsyncRedis, Depends(get_async_redis)]
def alterToken(redis: RedisDep, alter_token: str | None = Header(None)): def alterToken(redis: RedisDep, alter_token: str | None = Header(None)):
+32 -33
View File
@@ -1,4 +1,5 @@
import asyncio import asyncio
import logging
from api.types.colg import HistoryGold, OfficialGold, RealTimeGold from api.types.colg import HistoryGold, OfficialGold, RealTimeGold
from api.core.Gzip import register_gzip_request from api.core.Gzip import register_gzip_request
@@ -6,7 +7,7 @@ import json
from fastapi import APIRouter, Query from fastapi import APIRouter, Query
from api.core.Response import response from api.core.Response import response
from api.core.Redis import get_redis_info, get_redis_info_async from api.core.Redis import get_redis_info, get_redis_info_async
from api.dp import RedisDep from api.dp import AsyncRedisDep, RedisDep
from typing import Annotated from typing import Annotated
import requests import requests
from config.main import config from config.main import config
@@ -15,6 +16,7 @@ from api.core.Response import Return as Response
import aiohttp import aiohttp
router = APIRouter() router = APIRouter()
logger = logging.getLogger('dcalc.colg')
register_gzip_request(router) register_gzip_request(router)
@@ -39,6 +41,29 @@ payload_helper = {
'token': config.HELPER_TOKEN, 'token': config.HELPER_TOKEN,
} }
OFFICIAL_GOLD_URL = 'https://dzhu.qq.com/zangyi/activity/app'
OFFICIAL_GOLD_REGIONS = ('1', '2', '3A', '3B', '4', '5', '6', '7')
async def _fetch_official_gold() -> list[dict]:
timeout = aiohttp.ClientTimeout(total=10)
async with aiohttp.ClientSession(timeout=timeout) as session:
async def fetch_region(region: str) -> dict:
form = {**payload_helper, 'serverNo': region}
async with session.post(OFFICIAL_GOLD_URL, data=form) as resp:
resp.raise_for_status()
payload = await resp.json(content_type=None)
data = payload['data']
return {
'updataTime': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'region': f'跨{region}',
'goldDealRank': [int(item['price']) for item in data['godDealRank']],
'goldTopDealRank': [int(item['price']) for item in data['godTopDealRank']],
}
return await asyncio.gather(*(fetch_region(region) for region in OFFICIAL_GOLD_REGIONS))
def get_date_str(timestamp: int) -> str: def get_date_str(timestamp: int) -> str:
utc_time = datetime.fromtimestamp(timestamp, tz=timezone.utc) utc_time = datetime.fromtimestamp(timestamp, tz=timezone.utc)
@@ -47,7 +72,7 @@ def get_date_str(timestamp: int) -> str:
@router.get('/gold/realtime/', operation_id='realtimeGold', response_model=Response[list[RealTimeGold]]) @router.get('/gold/realtime/', operation_id='realtimeGold', response_model=Response[list[RealTimeGold]])
async def get_real_time_gold(redis: RedisDep) -> Response[list[RealTimeGold]]: def get_real_time_gold(redis: RedisDep) -> Response[list[RealTimeGold]]:
""" """
返回所有跨区的实时金价信息 返回所有跨区的实时金价信息
""" """
@@ -74,7 +99,7 @@ async def get_real_time_gold(redis: RedisDep) -> Response[list[RealTimeGold]]:
@router.get('/gold/history/', operation_id='historyGold', response_model=Response[HistoryGold] | Response[None]) @router.get('/gold/history/', operation_id='historyGold', response_model=Response[HistoryGold] | Response[None])
async def get_history_gold(region: Annotated[int, Query(..., description='跨区id')], timeRange: Annotated[int, Query(..., description='时间跨度天数')] = 7) -> Response[HistoryGold] | Response[None]: def get_history_gold(region: Annotated[int, Query(..., description='跨区id')], timeRange: Annotated[int, Query(..., description='时间跨度天数')] = 7) -> Response[HistoryGold] | Response[None]:
""" """
获取历史金价数据 获取历史金价数据
@@ -146,7 +171,7 @@ async def get_history_gold(region: Annotated[int, Query(..., description='跨区
@router.get('/gold/official/', operation_id='officialGold', response_model=Response[list[OfficialGold]]) @router.get('/gold/official/', operation_id='officialGold', response_model=Response[list[OfficialGold]])
async def get_official_gold(redis: RedisDep) -> Response[list[OfficialGold]]: async def get_official_gold(redis: AsyncRedisDep) -> Response[list[OfficialGold]]:
""" """
获取DNF官方金币寄售实时数据 获取DNF官方金币寄售实时数据
@@ -156,36 +181,10 @@ async def get_official_gold(redis: RedisDep) -> Response[list[OfficialGold]]:
- 该接口会返回所有跨区的官方金币寄售数据,包括最低成交价排名前5的价格和最高成交价排名前5的价格 - 该接口会返回所有跨区的官方金币寄售数据,包括最低成交价排名前5的价格和最高成交价排名前5的价格
- 数据来源于DNF官方提供的接口,可能会有一定的延迟和不稳定性 - 数据来源于DNF官方提供的接口,可能会有一定的延迟和不稳定性
""" """
official_url = 'https://dzhu.qq.com/zangyi/activity/app'
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
key = 'openapi:colg:real_time_gold_officical' key = 'openapi:colg:real_time_gold_officical'
list = ['1', '2', '3A', '3B', '4', '5', '6', '7']
try: try:
data = await get_redis_info_async(redis, key, _fetch_official_gold, 30 * 60)
async def get_data(): except Exception:
datas = [] logger.exception('fetch official gold failed')
async with aiohttp.ClientSession() as session:
tasks = []
for i in list:
payload_helper['serverNo'] = i
query = '&'.join(f'{k}={v}' for k, v in payload_helper.items())
tasks.append(session.post(official_url, data=query, headers=headers, timeout=10))
responses = await asyncio.gather(*tasks)
for index, resp in enumerate(responses):
res = json.loads(await resp.read())
datas.append(
{
'updataTime': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'region': f'跨{list[index]}',
'goldDealRank': [int(i.get('price')) for i in res['data']['godDealRank']],
'goldTopDealRank': [int(i.get('price')) for i in res['data']['godTopDealRank']],
}
)
return datas
except Exception as e:
print(e)
return response(data=[]) return response(data=[])
# 设置半小时过期一次
data = await get_redis_info_async(redis, key, get_data, 30 * 60)
return response(data=data) return response(data=data)
+10 -10
View File
@@ -4,7 +4,7 @@ from fastapi import APIRouter, Body, Request, Response
from api.core.Auth import createToken from api.core.Auth import createToken
from api.core.Response import response from api.core.Response import response
from api.core.Redis import get_redis_info, get_redis_info_async from api.core.Redis import get_redis_info, get_redis_info_async
from api.dp import RedisDep, AltersDep from api.dp import AsyncRedisDep, RedisDep, AltersDep
from core.basic.equipment import get_equipment from core.basic.equipment import get_equipment
from core.character.adventure import get_adv_info, get_adv_list from core.character.adventure import get_adv_info, get_adv_list
from core.basic.character import createCharacter from core.basic.character import createCharacter
@@ -21,7 +21,7 @@ register_gzip_request(router)
@router.get('/adventure') @router.get('/adventure')
async def get_adventure_info(request: Request, redis: RedisDep, name: str = None): def get_adventure_info(request: Request, redis: RedisDep, name: str = None):
adventure_info = get_redis_info(redis, 'dcalc:adventure', get_adv_list) adventure_info = get_redis_info(redis, 'dcalc:adventure', get_adv_list)
if name: if name:
total = [item for job in adventure_info for item in job.get('children', [])] total = [item for job in adventure_info for item in job.get('children', [])]
@@ -31,7 +31,7 @@ async def get_adventure_info(request: Request, redis: RedisDep, name: str = None
@router.get('/token/get/{alter}') @router.get('/token/get/{alter}')
async def getToken(redis: RedisDep, alter: str, request: Request, version: str = None, equVersion: str = '0'): def getToken(redis: RedisDep, alter: str, request: Request, version: str = None, equVersion: str = '0'):
if get_adv_info(alter) is None: if get_adv_info(alter) is None:
raise ResponseException('无效的职业') raise ResponseException('无效的职业')
token = createToken(alter, equVersion, redis) token = createToken(alter, equVersion, redis)
@@ -39,7 +39,7 @@ async def getToken(redis: RedisDep, alter: str, request: Request, version: str =
@router.get('/character') @router.get('/character')
async def get_character_info(request: Request, state: AltersDep, redis: RedisDep): def get_character_info(request: Request, state: AltersDep, redis: RedisDep):
def get_character(): def get_character():
return createCharacter(state.alter, state.equVersion).getInfo() return createCharacter(state.alter, state.equVersion).getInfo()
@@ -56,7 +56,7 @@ async def get_character_info(request: Request, state: AltersDep, redis: RedisDep
@router.get('/info/oath/{suitId}/{skillId}') @router.get('/info/oath/{suitId}/{skillId}')
async def get_oath_info(state: AltersDep, suitId: str, skillId: str, rarity: str, lv: int, redis: RedisDep): def get_oath_info(state: AltersDep, suitId: str, skillId: str, rarity: str, lv: int, redis: RedisDep):
def get_info(): def get_info():
skill = None skill = None
version = state.equVersion version = state.equVersion
@@ -73,7 +73,7 @@ async def get_oath_info(state: AltersDep, suitId: str, skillId: str, rarity: str
@router.get('/skill/{skillId}/{level}') @router.get('/skill/{skillId}/{level}')
async def get_skill_info(state: AltersDep, skillId: str, level: int, redis: RedisDep): def get_skill_info(state: AltersDep, skillId: str, level: int, redis: RedisDep):
""" """
获取技能信息 获取技能信息
""" """
@@ -99,7 +99,7 @@ async def get_skill_info(state: AltersDep, skillId: str, level: int, redis: Redi
@router.get('/dnfhelper/{uid}/list') @router.get('/dnfhelper/{uid}/list')
async def get_dnfhelper_list(uid: str, redis: RedisDep): async def get_dnfhelper_list(uid: str, redis: AsyncRedisDep):
cache_key = f'dnfhelper:{uid}' cache_key = f'dnfhelper:{uid}'
async def get_list(): async def get_list():
@@ -111,7 +111,7 @@ async def get_dnfhelper_list(uid: str, redis: RedisDep):
@router.get('/dnfhelper/{uid}/{id}/') @router.get('/dnfhelper/{uid}/{id}/')
async def get_dnfhelper_detail(uid: str, id: str, state: AltersDep, redis: RedisDep): async def get_dnfhelper_detail(uid: str, id: str, state: AltersDep, redis: AsyncRedisDep):
cache_key = f'dnfhelper:{uid}:{id}' cache_key = f'dnfhelper:{uid}:{id}'
async def get_detail(): async def get_detail():
@@ -123,7 +123,7 @@ async def get_dnfhelper_detail(uid: str, id: str, state: AltersDep, redis: Redis
@router.get('/dnfhelper/{uid}/') @router.get('/dnfhelper/{uid}/')
async def get_info_by_dnfhelper(uid: str, state: AltersDep, redis: RedisDep): async def get_info_by_dnfhelper(uid: str, state: AltersDep):
try: try:
result = await get_user_info_by_id(uid, state) result = await get_user_info_by_id(uid, state)
return response(data=result) return response(data=result)
@@ -134,7 +134,7 @@ async def get_info_by_dnfhelper(uid: str, state: AltersDep, redis: RedisDep):
@router.post('/skillTree/') @router.post('/skillTree/')
async def get_skill_by_code(state: AltersDep, code=Body(None)): def get_skill_by_code(state: AltersDep, code=Body(None)):
try: try:
skills = get_skill_tree_info(code.get('code', '')) skills = get_skill_tree_info(code.get('code', ''))
if skills.get('job', {}).get('class', '') != state.alter.replace('_carry', ''): if skills.get('job', {}).get('class', '') != state.alter.replace('_carry', ''):
+5 -5
View File
@@ -31,7 +31,7 @@ register_gzip_request(router)
@router.get('/skills/', operation_id='skillsList', response_model=SearchSkillResponse) @router.get('/skills/', operation_id='skillsList', response_model=SearchSkillResponse)
async def get_skills_list( def get_skills_list(
redis: RedisDep, redis: RedisDep,
skillName: Annotated[str | None, Query(description='技能名称,支持模糊查询')] = None, skillName: Annotated[str | None, Query(description='技能名称,支持模糊查询')] = None,
) -> SearchSkillResponse: ) -> SearchSkillResponse:
@@ -62,7 +62,7 @@ async def get_skills_list(
@router.get('/jobInfo/', operation_id='jobsInfo', response_model=JobInfoResponse) @router.get('/jobInfo/', operation_id='jobsInfo', response_model=JobInfoResponse)
async def get_jobs_info( def get_jobs_info(
redis: RedisDep, redis: RedisDep,
jobName: Annotated[str, Query(..., description='职业名称,支持模糊查询')] = None, jobName: Annotated[str, Query(..., description='职业名称,支持模糊查询')] = None,
) -> JobInfoResponse: ) -> JobInfoResponse:
@@ -98,7 +98,7 @@ async def get_jobs_info(
@router.get('/{jobId}/{jobGrowId}/skills/', operation_id='skillsByJob', response_model=SkillsListResponse) @router.get('/{jobId}/{jobGrowId}/skills/', operation_id='skillsByJob', response_model=SkillsListResponse)
async def get_cn_skills_by_job( def get_cn_skills_by_job(
redis: RedisDep, redis: RedisDep,
jobId: Annotated[str, Path(..., description='职业')], jobId: Annotated[str, Path(..., description='职业')],
jobGrowId: Annotated[str, Path(..., description='转职')], jobGrowId: Annotated[str, Path(..., description='转职')],
@@ -122,7 +122,7 @@ async def get_cn_skills_by_job(
@router.get('/{jobId}/{jobGrowId}/{skillId}/', operation_id='skillDetail', response_model=SkillDetailResponse) @router.get('/{jobId}/{jobGrowId}/{skillId}/', operation_id='skillDetail', response_model=SkillDetailResponse)
async def get_cn_skill_info( def get_cn_skill_info(
redis: RedisDep, redis: RedisDep,
jobId: Annotated[str, Path(..., description='职业')], jobId: Annotated[str, Path(..., description='职业')],
jobGrowId: Annotated[str, Path(..., description='转职')], jobGrowId: Annotated[str, Path(..., description='转职')],
@@ -164,7 +164,7 @@ async def get_cn_skill_info(
@router.get('/skillData/summary/', operation_id='skillDataSummary', response_model=SkillDataSummaryResponse) @router.get('/skillData/summary/', operation_id='skillDataSummary', response_model=SkillDataSummaryResponse)
async def get_skill_data_summary( def get_skill_data_summary(
redis: RedisDep, redis: RedisDep,
job: Annotated[str, Query(description='职业')], job: Annotated[str, Query(description='职业')],
skills: Annotated[ skills: Annotated[
+1 -3
View File
@@ -1,3 +1 @@
import redis """Database connection helpers live in :mod:`database.connect`."""
pool = redis.ConnectionPool(host='localhost', port=6379, decode_responses=True)
+27 -11
View File
@@ -1,11 +1,17 @@
from functools import cache from functools import cache
import redis import redis
import redis.asyncio as async_redis
from sqlalchemy import create_engine from sqlalchemy import create_engine
from sqlalchemy.orm import Session from sqlalchemy.engine import Engine
from sqlalchemy.pool import NullPool
from config.main import BACKEND_DIR, config from config.main import BACKEND_DIR, config
RedisPool = redis.ConnectionPool.from_url(config.REDIS_URL) # redis-py 8 defaults to RESP3. Keep the existing RESP2 wire contract so the
# client remains compatible with older Redis servers and proxies.
RedisPool = redis.ConnectionPool.from_url(config.REDIS_URL, protocol=2)
AsyncRedisPool = async_redis.ConnectionPool.from_url(config.REDIS_URL, protocol=2)
_db_engines: set[Engine] = set()
def get_redis_client(): def get_redis_client():
@@ -17,16 +23,26 @@ def get_redis():
yield redis_conn yield redis_conn
async def get_async_redis():
redis_conn = async_redis.Redis(connection_pool=AsyncRedisPool)
try:
yield redis_conn
finally:
await redis_conn.aclose()
@cache @cache
def get_db_engine(version='0'): def get_db_engine(version='0'):
db_path = BACKEND_DIR / 'dataFiles' / f'dcalc_{version}.db' db_path = BACKEND_DIR / 'dataFiles' / f'dcalc_{version}.db'
return create_engine( engine = create_engine(f'sqlite:///{db_path}')
f'sqlite:///{db_path}', _db_engines.add(engine)
connect_args={'check_same_thread': False}, return engine
poolclass=NullPool,
)
def get_db(): async def close_connection_pools() -> None:
with Session(get_db_engine()) as session: RedisPool.close()
yield session await AsyncRedisPool.aclose()
for engine in _db_engines:
engine.dispose()
_db_engines.clear()
get_db_engine.cache_clear()
+5 -1
View File
@@ -10,6 +10,7 @@ from api.core import Exceptions, Gzip, Middleware
from api.core.Logging import configure_logging from api.core.Logging import configure_logging
from api.main import api_router from api.main import api_router
from config.main import config from config.main import config
from database.connect import close_connection_pools
configure_logging() configure_logging()
@@ -19,7 +20,10 @@ async def lifespan(_: FastAPI):
print('dcalc is starting...') print('dcalc is starting...')
print('Application starting...') print('Application starting...')
print('Application startup complete.') print('Application startup complete.')
yield try:
yield
finally:
await close_connection_pools()
application = FastAPI(docs_url='/doc', redoc_url=None, lifespan=lifespan) application = FastAPI(docs_url='/doc', redoc_url=None, lifespan=lifespan)
+9 -5
View File
@@ -5,16 +5,20 @@ description = "A backend project for dcalc, a calculator application."
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.10"
dependencies = [ dependencies = [
"aiohttp>=3.13.3", "aiohttp>=3.14.3",
"fastapi[all]>=0.136.3", "fastapi[all]>=0.141.1",
"fastmcp>=3.3.1", "fastmcp>=3.3.1",
"redis>=5.2.1", "pydantic>=2.13.4",
"requests>=2.32.3", "pydantic-settings>=2.15.0",
"sqlalchemy>=2.0.38", "redis>=8.1.0",
"requests>=2.34.2",
"sqlalchemy>=2.0.52",
"uvicorn[standard]>=0.52.3",
] ]
[dependency-groups] [dependency-groups]
dev = [ dev = [
"httpx2>=2.10.0",
"pytest>=8.4.0", "pytest>=8.4.0",
"ruff>=0.15.15", "ruff>=0.15.15",
] ]
+65
View File
@@ -0,0 +1,65 @@
import asyncio
from api.routes import colg
class FakeResponse:
def __init__(self, region):
self.region = region
async def __aenter__(self):
return self
async def __aexit__(self, exc_type, exc, traceback):
return False
def raise_for_status(self):
return None
async def json(self, content_type=None):
await asyncio.sleep(0)
return {
'data': {
'godDealRank': [{'price': '100'}, {'price': '101'}],
'godTopDealRank': [{'price': '200'}, {'price': '201'}],
}
}
class FakeSession:
def __init__(self, calls, **kwargs):
self.calls = calls
self.kwargs = kwargs
async def __aenter__(self):
return self
async def __aexit__(self, exc_type, exc, traceback):
return False
def post(self, url, data):
self.calls.append((url, data))
return FakeResponse(data['serverNo'])
def test_fetch_official_gold_uses_async_http_with_isolated_forms(monkeypatch):
calls = []
sessions = []
def create_session(**kwargs):
session = FakeSession(calls, **kwargs)
sessions.append(session)
return session
monkeypatch.setattr(colg.aiohttp, 'ClientSession', create_session)
result = asyncio.run(colg._fetch_official_gold())
assert [data['serverNo'] for _, data in calls] == list(colg.OFFICIAL_GOLD_REGIONS)
assert all(url == colg.OFFICIAL_GOLD_URL for url, _ in calls)
assert len({id(data) for _, data in calls}) == len(colg.OFFICIAL_GOLD_REGIONS)
assert [item['region'] for item in result] == [f'跨{region}' for region in colg.OFFICIAL_GOLD_REGIONS]
assert result[0]['goldDealRank'] == [100, 101]
assert result[0]['goldTopDealRank'] == [200, 201]
assert sessions[0].kwargs['timeout'].total == 10
assert 'serverNo' not in colg.payload_helper
+64
View File
@@ -0,0 +1,64 @@
import asyncio
import json
from api.core.Redis import get_redis_info, get_redis_info_async
from config.main import config
from database.connect import AsyncRedisPool, RedisPool
class FakeRedis:
def __init__(self, value=None, fail_write=False):
self.value = value
self.fail_write = fail_write
self.set_calls = []
def get(self, key):
return self.value
def set(self, key, value, **kwargs):
if self.fail_write:
raise RuntimeError('Redis unavailable')
self.set_calls.append((key, value, kwargs))
class FakeAsyncRedis(FakeRedis):
async def get(self, key):
return self.value
async def set(self, key, value, **kwargs):
super().set(key, value, **kwargs)
def test_shared_pool_keeps_resp2_compatibility():
assert RedisPool.connection_kwargs['protocol'] == 2
assert AsyncRedisPool.connection_kwargs['protocol'] == 2
def test_sync_cache_sets_value_and_ttl_in_one_command(monkeypatch):
monkeypatch.setattr(config, 'DEBUG_MODE', False)
redis = FakeRedis()
result = get_redis_info(redis, 'key', lambda: {'value': 1}, expire=60, without_gzip=True)
assert result == {'value': 1}
assert redis.set_calls == [('key', json.dumps(result), {'ex': 60})]
def test_sync_cache_write_failure_does_not_fail_request(monkeypatch):
monkeypatch.setattr(config, 'DEBUG_MODE', False)
redis = FakeRedis(fail_write=True)
assert get_redis_info(redis, 'key', lambda: {'value': 1}, without_gzip=True) == {'value': 1}
def test_async_cache_awaits_async_redis_io(monkeypatch):
monkeypatch.setattr(config, 'DEBUG_MODE', False)
redis = FakeAsyncRedis()
async def load():
return {'value': 1}
result = asyncio.run(get_redis_info_async(redis, 'key', load, expire=60, without_gzip=True))
assert result == {'value': 1}
assert redis.set_calls == [('key', json.dumps(result), {'ex': 60})]
+635 -413
View File
File diff suppressed because it is too large Load Diff