feat:添加colg对接功能

This commit is contained in:
2026-11-03 21:44:30 +08:00
parent 3937185d0a
commit 4709ea4328
13 changed files with 1742 additions and 79 deletions
+45 -62
View File
@@ -1,19 +1,17 @@
import asyncio
import logging
from api.types.colg import HistoryGold, OfficialGold, RealTimeGold
from api.utils import official_gold, thirdpart_gold
from api.core.Gzip import register_gzip_request
import json
from fastapi import APIRouter, Query
from fastapi import APIRouter, BackgroundTasks, Depends, Query
from redis.asyncio import Redis as AsyncRedis
from api.core.Response import response
from api.core.Redis import get_redis_info, get_redis_info_async
from api.dp import AsyncRedisDep, RedisDep
from typing import Annotated
import requests
from config.main import config
from datetime import datetime, timezone, timedelta
from api.core.Response import Return as Response
import aiohttp
router = APIRouter()
logger = logging.getLogger('dcalc.colg')
@@ -35,36 +33,6 @@ url = 'https://poservice.colg.cn/api/v1/trend/getCoinTrendDataKol'
payload = {'token': config.COLG_TOKEN, 'area': 0, 'time_type': 'other', 'start_time': '2026-01-06', 'end_time': '2026-01-06'}
payload_helper = {
'r': 'godRank/godDealRank',
'userId': config.HELPER_ID,
'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:
utc_time = datetime.fromtimestamp(timestamp, tz=timezone.utc)
beijing_time = utc_time + timedelta(hours=8)
@@ -72,30 +40,13 @@ def get_date_str(timestamp: int) -> str:
@router.get('/gold/realtime/', operation_id='realtimeGold', response_model=Response[list[RealTimeGold]])
def get_real_time_gold(redis: RedisDep) -> Response[list[RealTimeGold]]:
"""
返回所有跨区的实时金价信息
"""
key = 'openapi:colg:real_time_gold'
try:
async def get_real_time_gold(redis: Annotated[AsyncRedis, Depends(thirdpart_gold.get_redis)], background_tasks: BackgroundTasks) -> Response[list[RealTimeGold]]:
"""获取网站金币比例,数据为各跨区首次报价前200条的平均万金币/元。
def get_gold_data():
res = []
for region_name, region_id in regionMap.items():
payload['area'] = region_id
payload['start_time'] = datetime.now().strftime('%Y-%m-%d')
payload['end_time'] = datetime.now().strftime('%Y-%m-%d')
response = requests.post(url, headers={'Content-Type': 'application/json'}, data=json.dumps(payload))
response.raise_for_status()
resp = response.json().get('data', {})
res.append({'updataTime': get_date_str(resp['x'][-1]), 'region': region_name, 'goldPrice': resp['y'][-1]})
return res
except Exception:
# print(f"File not found: {e}")
return response(data=[])
# 设置半小时过期一次
data = get_redis_info(redis, key, get_gold_data, 30 * 60)
return response(data=data)
updataTime为数字秒级时间戳,code为跨区标识。与thirdpart接口共用Redis缓存和
后台刷新任务:30分钟自动更新,也支持按需补齐,逐区间隔8秒;初始化未齐时响应体code=202。
"""
return await get_thirdpart_gold(redis, background_tasks)
@router.get('/gold/history/', operation_id='historyGold', response_model=Response[HistoryGold] | Response[None])
@@ -171,7 +122,7 @@ def get_history_gold(region: Annotated[int, Query(..., description='跨区id')],
@router.get('/gold/official/', operation_id='officialGold', response_model=Response[list[OfficialGold]])
async def get_official_gold(redis: AsyncRedisDep) -> Response[list[OfficialGold]]:
async def get_official_gold(redis: Annotated[AsyncRedis, Depends(thirdpart_gold.get_redis)]) -> Response[list[OfficialGold]]:
"""
获取DNF官方金币寄售实时数据
@@ -180,11 +131,43 @@ async def get_official_gold(redis: AsyncRedisDep) -> Response[list[OfficialGold]
Note:
- 该接口会返回所有跨区的官方金币寄售数据,包括最低成交价排名前5的价格和最高成交价排名前5的价格
- 数据来源于DNF官方提供的接口,可能会有一定的延迟和不稳定性
- 30分钟自动刷新,失败保留最多24小时的旧值;认证失败且配置了BARK_URL时发送通知
"""
key = 'openapi:colg:real_time_gold_officical'
try:
data = await get_redis_info_async(redis, key, _fetch_official_gold, 30 * 60)
data = await official_gold.get_data(redis)
except Exception:
logger.exception('fetch official gold failed')
return response(data=[])
return response(code=503, data=[], message='官方金价缓存暂不可用')
if not data:
return response(code=503, data=[], message='官方金价暂不可用,请稍后重试')
return response(data=data)
@router.get('/gold/thirdpart/', operation_id='thirdpartGold', response_model=Response[list[RealTimeGold]])
async def get_thirdpart_gold(redis: Annotated[AsyncRedis, Depends(thirdpart_gold.get_redis)], background_tasks: BackgroundTasks) -> Response[list[RealTimeGold]]:
"""获取跨1~跨8(含跨3A/3B)的第三方首批报价均值,单位:万金币/元。
不限制最低总价;各平台首次返回的有效报价汇总、去重、按单价取前200条,
对每条报价的1/Price取算术平均,不按库存或份数加权。保留上游原始价格,
不额外计算交易手续费。该接口无上游分页参数,前200条是本接口的样本口径。
每30分钟自动后台刷新,跨区页面最短间隔8秒(每分钟不超过8次),Redis锁避免重复刷新。
初始化跨区未齐时响应体code=202并注明进度;9区缓存齐全才返回code=200。
刷新中可返回部分已完成跨区,缺失跨区不补0。
已有缓存即时返回,失败保留原更新时间,旧值最多保留24小时。
该接口在DEBUG模式下也使用缓存,Redis不可用时不直接抓取上游。
"""
try:
data = await thirdpart_gold.read_cache(redis)
except Exception:
logger.exception('read thirdpart gold cache failed')
return response(code=503, data=[], message='第三方金价缓存暂不可用')
try:
await thirdpart_gold.schedule_refresh(redis, background_tasks, data)
except Exception:
logger.exception('schedule thirdpart gold refresh failed')
return response(code=200 if data else 503, data=data, message='第三方金价刷新暂不可用')
if len(data) < len(thirdpart_gold.REGIONS):
return response(code=202, data=data,
message=f'第三方金价缓存尚未齐全({len(data)}/{len(thirdpart_gold.REGIONS)}跨区),后台刷新中或等待重试')
return response(data=data)