This commit is contained in:
2026-08-18 15:21:53 +08:00
parent 5ccf31aa20
commit efcbc49096
4 changed files with 249 additions and 7 deletions
+13 -7
View File
@@ -1,6 +1,6 @@
from api.core.Gzip import register_gzip_request
import json
from fastapi import APIRouter, Body, Request
from fastapi import APIRouter, Body, Request, Response
from api.core.Auth import createToken
from api.core.Response import response
from api.core.Redis import get_redis_info, get_redis_info_async
@@ -13,6 +13,7 @@ from core.open.skillTree import get_skill_tree_info
from api.core.exception import ResponseException
from api.utils.skill_detail import SKILL_DETAIL_NORMALIZE_VERSION, normalize_skill_detail, skill_detail_for_level
from api.utils.openapi_data import openapi_locale_path
from api.utils.character_response import character_response_encoding, get_character_response_bytes
router = APIRouter()
@@ -39,14 +40,19 @@ async def getToken(redis: RedisDep, alter: str, request: Request, version: str =
@router.get('/character')
async def get_character_info(request: Request, state: AltersDep, redis: RedisDep):
character = createCharacter(state.alter, state.equVersion)
def get_character():
return character.getInfo()
return createCharacter(state.alter, state.equVersion).getInfo()
info = get_redis_info(redis, f'dcalc:character:{state.alter}:{state.equVersion}', get_character)
# info = character.getInfo()
return response(data=info)
def load_info():
return get_redis_info(redis, f'dcalc:character:{state.alter}:{state.equVersion}', get_character)
encoding = character_response_encoding(request.headers.get('accept-encoding'))
content = get_character_response_bytes(redis, state.alter, state.equVersion, encoding, load_info)
headers = {}
if encoding == 'gzip':
headers['Content-Encoding'] = 'gzip'
headers['Vary'] = 'Accept-Encoding'
return Response(content=content, media_type='application/json', headers=headers)
@router.get('/info/oath/{suitId}/{skillId}')
+111
View File
@@ -0,0 +1,111 @@
from collections import OrderedDict
from collections.abc import Callable
import gzip
import logging
from threading import RLock
from config.main import config
from api.core.Response import response
logger = logging.getLogger('dcalc.character_response')
CHARACTER_RESPONSE_CACHE_VERSION = 'v1'
CHARACTER_RESPONSE_LOCAL_CACHE_MAX_BYTES = 16 * 1024 * 1024
CharacterInfoLoader = Callable[[], dict]
CharacterResponseCacheKey = tuple[str, str, str]
_local_cache: OrderedDict[CharacterResponseCacheKey, bytes] = OrderedDict()
_local_cache_size = 0
_local_cache_lock = RLock()
def character_response_encoding(accept_encoding: str | None) -> str:
"""Match the application's GZipMiddleware negotiation behavior."""
return 'gzip' if 'gzip' in (accept_encoding or '').lower() else 'identity'
def character_response_cache_key(alter: str, equ_version: str, encoding: str) -> str:
return f'dcalc:character-response:{CHARACTER_RESPONSE_CACHE_VERSION}:{alter}:{equ_version}:{encoding}'
def serialize_character_response(info: dict, encoding: str) -> bytes:
content = response(data=info).model_dump_json().encode('utf-8')
if encoding == 'gzip':
return gzip.compress(content, compresslevel=9, mtime=0)
return content
def clear_character_response_local_cache() -> None:
global _local_cache_size
with _local_cache_lock:
_local_cache.clear()
_local_cache_size = 0
def _get_local_cache(key: CharacterResponseCacheKey) -> bytes | None:
with _local_cache_lock:
content = _local_cache.pop(key, None)
if content is not None:
_local_cache[key] = content
return content
def _set_local_cache(key: CharacterResponseCacheKey, content: bytes) -> None:
global _local_cache_size
if len(content) > CHARACTER_RESPONSE_LOCAL_CACHE_MAX_BYTES:
return
with _local_cache_lock:
previous = _local_cache.pop(key, None)
if previous is not None:
_local_cache_size -= len(previous)
_local_cache[key] = content
_local_cache_size += len(content)
while _local_cache_size > CHARACTER_RESPONSE_LOCAL_CACHE_MAX_BYTES:
_, evicted = _local_cache.popitem(last=False)
_local_cache_size -= len(evicted)
def _get_redis_cache(redis, key: str) -> bytes | None:
if config.DEBUG_MODE or redis is None:
return None
try:
content = redis.get(key)
return bytes(content) if content is not None else None
except Exception:
logger.warning('read character response cache failed key=%s', key, exc_info=True)
return None
def _set_redis_cache(redis, key: str, content: bytes) -> None:
if config.DEBUG_MODE or redis is None:
return
try:
redis.set(key, content)
except Exception:
logger.warning('write character response cache failed key=%s', key, exc_info=True)
def get_character_response_bytes(
redis,
alter: str,
equ_version: str,
encoding: str,
load_info: CharacterInfoLoader,
) -> bytes:
local_key = (alter, equ_version, encoding)
content = _get_local_cache(local_key)
if content is not None:
return content
redis_key = character_response_cache_key(alter, equ_version, encoding)
content = _get_redis_cache(redis, redis_key)
if content is None:
content = serialize_character_response(load_info(), encoding)
_set_redis_cache(redis, redis_key, content)
_set_local_cache(local_key, content)
return content