Skip to content

Commit 1f52bc4

Browse files
committed
feat: enhance error handling and caching for search and trending APIs
1 parent 240a71c commit 1f52bc4

File tree

2 files changed

+93
-95
lines changed

2 files changed

+93
-95
lines changed

_search.py

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ async def checkSum(data):
3838
try:
3939
data = await decryptData(data.get('data'))
4040
except Exception as e:
41+
logging.error(e)
4142
raise HTTPException("Invalid Request")
4243
return json.loads(data)
4344

@@ -115,6 +116,9 @@ async def link_keywords(keyword):
115116
@searchRouter.api_route('/search', dependencies=[Depends(RateLimiter(times=3, seconds=1))], methods=['POST'],
116117
name='search')
117118
async def search(request: Request, background_tasks: BackgroundTasks):
119+
"""
120+
搜索接口
121+
"""
118122
data = await request.json()
119123
data = await checkSum(data)
120124
keyword, page, size = data.get('keyword'), data.get('page'), data.get('size')
@@ -180,30 +184,51 @@ async def keyword(request: Request):
180184

181185

182186
@searchRouter.api_route('/detail', methods=['POST'], name='detail',
183-
dependencies=[Depends(RateLimiter(times=1, seconds=3))])
184-
async def detail(request: Request):
187+
dependencies=[Depends(RateLimiter(times=2, seconds=1))])
188+
async def detail(request: Request, background_tasks: BackgroundTasks):
185189
data = await request.json()
186190
data = await checkSum(data)
187191
try:
188192
id = data.get('id')
189193
except Exception as e:
190194
return JSONResponse({"error": "Invalid Request, missing param: id"}, status_code=400,
191195
headers={"X-Error": str(e)})
192-
vv = await generate_vv_detail()
193-
url = f"https://api.olelive.com/v1/pub/vod/detail/{id}/true?_vv={vv}"
194-
headers = {
195-
'User-Agent': _getRandomUserAgent(),
196-
'Referer': 'https://www.olevod.com/',
197-
'Origin': 'https://www.olevod.com/',
198-
}
196+
197+
# Try to return cached detail (cache for 30 minutes)
198+
redis_key = f"detail_{id}"
199199
try:
200-
async with httpx.AsyncClient() as client:
201-
response = await client.get(url, headers=headers)
202-
response_data = response.json()
203-
return JSONResponse(response_data, status_code=200)
204-
except:
205-
return JSONResponse({"error": "Upstream Error"}, status_code=501)
206-
# direct play https://player.viloud.tv/embed/play?url=https://www.olevod.com/vod/detail/5f4b3b7b7f3c1d0001b2b3b3&autoplay=1
200+
cached = await redis_get_key(redis_key)
201+
if cached:
202+
cached_data = json.loads(cached)
203+
# mark as cached so clients can know
204+
if isinstance(cached_data, dict):
205+
cached_data["msg"] = "cached"
206+
return JSONResponse(cached_data, status_code=200)
207+
except Exception:
208+
# if redis lookup fails, continue to fetch upstream
209+
vv = await generate_vv_detail()
210+
url = f"https://api.olelive.com/v1/pub/vod/detail/{id}/true?_vv={vv}"
211+
headers = {
212+
'User-Agent': _getRandomUserAgent(),
213+
'Referer': 'https://www.olevod.com/',
214+
'Origin': 'https://www.olevod.com/',
215+
}
216+
try:
217+
async with httpx.AsyncClient() as client:
218+
response = await client.get(url, headers=headers)
219+
response_data = response.json()
220+
# cache the response for 30 minutes (1800 seconds) in background
221+
try:
222+
background_tasks.add_task(redis_set_key, redis_key, json.dumps(response_data), ex=1800)
223+
except Exception:
224+
# if scheduling background task fails, attempt immediate set but don't block on errors
225+
try:
226+
await redis_set_key(redis_key, json.dumps(response_data), ex=1800)
227+
except Exception:
228+
pass
229+
return JSONResponse(response_data, status_code=200)
230+
except Exception:
231+
return JSONResponse({"error": "Upstream Error"}, status_code=501)
207232

208233

209234
@searchRouter.api_route('/report/keyword', methods=['POST'], name='report_keyword',

_trend.py

Lines changed: 52 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -16,128 +16,101 @@
1616

1717
trendingRoute = APIRouter(prefix='/api/trending', tags=['Trending'])
1818

19+
ALLOWED_PERIODS = {'day', 'week', 'month', 'all'}
20+
ALLOWED_TYPE_IDS = {1, 2, 3, 4}
21+
1922

2023
async def gen_url(typeID: int, period: str, amount=10):
21-
"""
22-
传入的值必须经过检查,否则可能会导致 API 请求失败。
23-
"""
24-
if period not in ['day', 'week', 'month', 'all']:
25-
return JSONResponse(status_code=400,
26-
content={'error': 'Invalid period parameter, must be one of: day, week, month, all'})
27-
if typeID not in [1, 2, 3, 4]:
28-
return JSONResponse(status_code=400, content={
29-
'error': 'Invalid typeID parameter, must be one of: 1 --> 电影, 2 --> 电视剧(连续剧), 3 --> 综艺, 4 --> 动漫'})
24+
if period not in ALLOWED_PERIODS:
25+
return JSONResponse(status_code=400, content={'error': 'Invalid period parameter, must be one of: day, week, month, all'})
26+
if typeID not in ALLOWED_TYPE_IDS:
27+
return JSONResponse(status_code=400, content={'error': 'Invalid typeID parameter, must be one of: 1 --> 电影, 2 --> 电视剧(连续剧), 3 --> 综艺, 4 --> 动漫'})
3028
vv = await gen_vv()
31-
url = f"https://api.olelive.com/v1/pub/index/vod/data/rank/{period}/{typeID}/{amount}?_vv={vv}"
32-
return url
29+
return f"https://api.olelive.com/v1/pub/index/vod/data/rank/{period}/{typeID}/{amount}?_vv={vv}"
3330

3431

3532
async def gen_url_v2(typeID: int, amount=10):
36-
"""
37-
传入的值必须经过检查,否则可能会导致 API 请求失败。
38-
"""
39-
if typeID not in [1, 2, 3, 4]:
40-
return JSONResponse(status_code=400, content={
41-
'error': 'Invalid typeID parameter, must be one of: 1 --> 电影, 2 --> 电视剧(连续剧), 3 --> 综艺, 4 --> 动漫'})
33+
if typeID not in ALLOWED_TYPE_IDS:
34+
return JSONResponse(status_code=400, content={'error': 'Invalid typeID parameter, must be one of: 1 --> 电影, 2 --> 电视剧(连续剧), 3 --> 综艺, 4 --> 动漫'})
4235
vv = await gen_vv()
43-
url = f"https://api.olelive.com/v1/pub/index/vod/hot/{typeID}/0/{amount}?_vv={vv}"
44-
return url
36+
return f"https://api.olelive.com/v1/pub/index/vod/hot/{typeID}/0/{amount}?_vv={vv}"
4537

4638

4739
@trendingRoute.post('/{period}/trend')
4840
async def fetch_trending_data(request: Request, period: Optional[str] = 'day'):
49-
"""
50-
Fetch trending data from the OLE API.
51-
:param request: The incoming request.
52-
:parameter period: The period of time to fetch trending data for. --> str Options: 'day', 'week', 'month', 'all'
53-
:parameter typeID: The type ID of the item. --> int
54-
typeID docs:
55-
1: 电影
56-
2: 电视剧(连续剧)
57-
3: 综艺
58-
4: 动漫
59-
:parameter amount: The number of items to fetch. --> int default: 10
60-
"""
6141
try:
62-
data = await request.json()
42+
body = await request.json()
6343
try:
64-
typeID = data['params']['typeID']
44+
typeID = body['params']['typeID']
6545
logging.info(f"typeID1: {typeID}")
66-
except KeyError as e:
67-
return JSONResponse(status_code=400, content={'error': f"Where is your param?"})
46+
except KeyError:
47+
return JSONResponse(status_code=400, content={'error': "Where is your param?"})
6848
except JSONDecodeError as e:
6949
logging.error(f"JSONDecodeError: {e}, hint: request.json() failed, step fetch_trending_data")
70-
return JSONResponse(status_code=400, content={'error': f"Where is your param?"})
50+
return JSONResponse(status_code=400, content={'error': "Where is your param?"})
51+
7152
if period is None:
7253
logging.error(f"period: {period}, hint: period is None, step fetch_trending_data")
7354
return JSONResponse(status_code=400, content={'error': 'Missing required parameters: period'})
7455
if typeID is None:
7556
logging.info(f"typeID: {typeID}, hint:typeID is None, step fetch_trending_data")
7657
return JSONResponse(status_code=400, content={'error': 'Missing required parameters: typeID'})
77-
if period not in ['day', 'week', 'month', 'all']:
58+
if period not in ALLOWED_PERIODS:
7859
logging.error(f"period: {period}, hint:period not in ['day', 'week', 'month', 'all]")
79-
return JSONResponse(status_code=400,
80-
content={'error': 'Invalid period parameter, must be one of: day, week, month, all'})
81-
if typeID not in [1, 2, 3, 4]:
60+
return JSONResponse(status_code=400, content={'error': 'Invalid period parameter, must be one of: day, week, month, all'})
61+
if typeID not in ALLOWED_TYPE_IDS:
8262
logging.error(f"typeID: {typeID}, hint:typeID not in [1,2,3,4]")
83-
return JSONResponse(status_code=400, content={
84-
'error': 'Invalid typeID parameter, must be one of: 1 --> 电影, 2 --> 电视剧(连续剧), 3 --> 综艺, 4 --> 动漫'})
63+
return JSONResponse(status_code=400, content={'error': 'Invalid typeID parameter, must be one of: 1 --> 电影, 2 --> 电视剧(连续剧), 3 --> 综艺, 4 --> 动漫'})
64+
8565
url = await gen_url(typeID, period, amount=10)
8666
logging.info(f"Fetching trending data from: {url}")
67+
68+
resp = None
69+
api_payload = None
8770
try:
8871
async with httpx.AsyncClient() as client:
89-
response = await client.get(url, headers={'User-Agent': _getRandomUserAgent()}, timeout=30)
90-
data = response.json()
91-
return JSONResponse(status_code=200, content=data)
72+
resp = await client.get(url, headers={'User-Agent': _getRandomUserAgent()}, timeout=30)
73+
api_payload = resp.json()
74+
return JSONResponse(status_code=200, content=api_payload)
9275
except httpx.RequestError as e:
93-
print(data)
76+
logging.debug(f"snapshot: {api_payload}")
9477
return JSONResponse(status_code=500, content={'error': f"An error occurred: {e}"})
9578
except httpx.HTTPStatusError as e:
96-
print(data)
79+
logging.debug(f"snapshot: {api_payload}")
9780
return JSONResponse(status_code=500, content={'error': f"An HTTP error occurred: {e}"})
9881
except Exception as e:
99-
print(data)
100-
return JSONResponse(status_code=500, content={'error': f"An error occurred: {e}, response: {response.text}"})
82+
raw_text = resp.text if resp is not None else ""
83+
logging.debug(f"snapshot: {api_payload}")
84+
return JSONResponse(status_code=500, content={'error': f"An error occurred: {e}, response: {raw_text}"})
10185

10286

10387
@trendingRoute.api_route('/v2/{typeID}', methods=['POST'], dependencies=[Depends(RateLimiter(times=2, seconds=1))])
10488
async def fetch_trending_data_v2(request: Request, typeID: Optional[int] = None):
105-
"""
106-
Fetch trending data from the OLE API.
107-
:param request: The incoming request.
108-
:parameter typeID: The type ID of the item. --> int
109-
typeID docs:
110-
1: 电影
111-
2: 电视剧(连续剧)
112-
3: 综艺
113-
4: 动漫
114-
:parameter amount: The number of items to fetch. --> int default: 10
115-
"""
11689
try:
11790
amount = request.query_params['amount']
118-
except KeyError as e:
91+
except KeyError:
11992
amount = 10
93+
12094
if typeID is None:
12195
logging.info(f"typeID: {typeID}, hint:typeID is None, step fetch_trending_data")
12296
return JSONResponse(status_code=400, content={'error': 'Missing required parameters: typeID'})
123-
if typeID not in [1, 2, 3, 4]:
97+
if typeID not in ALLOWED_TYPE_IDS:
12498
logging.error(f"typeID: {typeID}, hint:typeID not in [1,2,3,4]")
125-
return JSONResponse(status_code=400, content={
126-
'error': 'Invalid typeID parameter, must be one of: 1 --> 电影, 2 --> 电视剧(连续剧), 3 --> 综艺, 4 --> 动漫'})
99+
return JSONResponse(status_code=400, content={'error': 'Invalid typeID parameter, must be one of: 1 --> 电影, 2 --> 电视剧(连续剧), 3 --> 综艺, 4 --> 动漫'})
100+
127101
redis_key = f"trending_v2_cache_{datetime.datetime.now().strftime('%Y-%m-%d')}_{typeID}_{amount}"
128-
if await get_key(redis_key):
102+
cached = await get_key(redis_key)
103+
if cached:
129104
logging.info(f"Hit cache for key: {redis_key}")
130-
data = await get_key(redis_key)
131-
data = json.loads(data)
132-
return JSONResponse(status_code=200, content=data)
133-
else:
134-
url = await gen_url_v2(typeID, amount)
135-
logging.info(f"Fetching trending data from: {url}")
136-
try:
137-
async with httpx.AsyncClient() as client:
138-
response = await client.get(url, headers={'User-Agent': _getRandomUserAgent()}, timeout=30)
139-
data = json.dumps(response.json())
140-
await set_key(redis_key, data, 60 * 60 * 24)
141-
return JSONResponse(status_code=200, content=json.loads(data))
142-
except httpx.RequestError as e:
143-
return JSONResponse(status_code=500, content={'error': f"An error occurred: {e}"})
105+
return JSONResponse(status_code=200, content=json.loads(cached))
106+
107+
url = await gen_url_v2(typeID, amount)
108+
logging.info(f"Fetching trending data from: {url}")
109+
try:
110+
async with httpx.AsyncClient() as client:
111+
response = await client.get(url, headers={'User-Agent': _getRandomUserAgent()}, timeout=30)
112+
payload = json.dumps(response.json())
113+
await set_key(redis_key, payload, 60 * 60 * 24)
114+
return JSONResponse(status_code=200, content=json.loads(payload))
115+
except httpx.RequestError as e:
116+
return JSONResponse(status_code=500, content={'error': f"An error occurred: {e}"})

0 commit comments

Comments
 (0)