This commit is contained in:
2026-03-09 18:30:23 +08:00
parent fffee149ec
commit 621023bf34
10 changed files with 955 additions and 241 deletions
+5 -4
View File
@@ -1,14 +1,13 @@
import base64 import base64
import json import json
from typing import Generic, TypeVar from typing import Generic, TypeVar
from pydantic import Field from pydantic import Field, BaseModel
from pydantic.generics import GenericModel
import gzip # 导入 gzip 模块 import gzip # 导入 gzip 模块
from io import BytesIO # 从 io 模块导入 BytesIO 类 from io import BytesIO # 从 io 模块导入 BytesIO 类
T = TypeVar('T') T = TypeVar('T')
class Return(GenericModel, Generic[T]): class Return(BaseModel, Generic[T]):
code: int = Field(..., description='响应状态码') code: int = Field(..., description='响应状态码')
"""响应状态码""" """响应状态码"""
message: str = Field(..., description='响应消息') message: str = Field(..., description='响应消息')
@@ -16,6 +15,7 @@ class Return(GenericModel, Generic[T]):
data: T = Field(..., description='响应数据') data: T = Field(..., description='响应数据')
"""响应数据""" """响应数据"""
# 定义一个函数,将字符串压缩为 gzip 格式,并进行 base64 编码后返回结果 # 定义一个函数,将字符串压缩为 gzip 格式,并进行 base64 编码后返回结果
def gzip_str(to_gzip: str) -> str: def gzip_str(to_gzip: str) -> str:
out = BytesIO() # 创建一个 BytesIO 对象 out = BytesIO() # 创建一个 BytesIO 对象
@@ -24,9 +24,10 @@ def gzip_str(to_gzip: str) -> str:
return base64.b64encode(out.getvalue()).decode() # 对压缩后的数据使用 base64 编码并返回结果 return base64.b64encode(out.getvalue()).decode() # 对压缩后的数据使用 base64 编码并返回结果
def response(*, code=200, data: list | dict | str, message='Success', zip=False)-> Return: def response(*, code=200, data: list | dict | str, message='Success', zip=False) -> Return:
if zip: if zip:
import gzip import gzip
data = base64.b64encode(gzip.compress(json.dumps(data).encode())) data = base64.b64encode(gzip.compress(json.dumps(data).encode()))
return Return(code=code, message=message, data=data) return Return(code=code, message=message, data=data)
+34 -28
View File
@@ -34,6 +34,12 @@ 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 = {'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,
}
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)
@@ -41,13 +47,14 @@ def get_date_str(timestamp: int) -> str:
return beijing_time.strftime('%Y-%m-%d %H:%M:%S') return beijing_time.strftime('%Y-%m-%d %H:%M:%S')
@router.get('/gold/realtime/', operation_id='realTimeGold') @router.get('/gold/realtime/', operation_id='realtime_gold',response_model=Response[list[RealTimeGold]])
async def get_real_time_gold(redis: RedisDep) -> Response[list[RealTimeGold]]: async def get_real_time_gold(redis: RedisDep) -> Response[list[RealTimeGold]]:
""" """
返回所有跨区的实时金价信息 返回所有跨区的实时金价信息
""" """
key = 'openapi:colg:real_time_gold' key = 'openapi:colg:real_time_gold'
try: try:
def get_gold_data(): def get_gold_data():
res = [] res = []
for region_name, region_id in regionMap.items(): for region_name, region_id in regionMap.items():
@@ -67,7 +74,7 @@ async def get_real_time_gold(redis: RedisDep) -> Response[list[RealTimeGold]]:
return response(data=data) return response(data=data)
@router.get('/gold/history/', operation_id='historyGold') @router.get('/gold/history/', operation_id='history_gold',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]: async def get_history_gold(region: Annotated[int, Query(..., description='跨区id')], timeRange: Annotated[int, Query(..., description='时间跨度天数')] = 7) -> Response[HistoryGold] | Response[None]:
""" """
获取历史金价数据 获取历史金价数据
@@ -139,43 +146,42 @@ async def get_history_gold(region: Annotated[int, Query(..., description='跨区
return response(data={'region': region_name, 'data': data, 'echartsData': echartsData}) return response(data={'region': region_name, 'data': data, 'echartsData': echartsData})
@router.get('/gold/official/', operation_id='officialGold') @router.get('/gold/official/', operation_id='official_gold',response_model=Response[list[OfficialGold]])
async def get_official_gold(redis: RedisDep) -> Response[list[OfficialGold]]: async def get_official_gold(redis: RedisDep) -> Response[list[OfficialGold]]:
""" """
获取官方金币寄售实时数据 获取DNF官方金币寄售实时数据
Returns:
Response[list[OfficialGold]]: 包含官方金币寄售数据的响应对象
Note:
- 该接口会返回所有跨区的官方金币寄售数据,包括最低成交价排名前5的价格和最高成交价排名前5的价格
- 数据来源于DNF官方提供的接口,可能会有一定的延迟和不稳定性
""" """
official_url = 'https://dzhu.qq.com/zangyi/activity/app' official_url = 'https://dzhu.qq.com/zangyi/activity/app'
headers = { headers = {'Content-Type': 'application/x-www-form-urlencoded'}
'Content-Type': 'application/x-www-form-urlencoded'
}
params = {
'r': 'godRank/godDealRank',
'userId': config.HELPER_ID,
'token': config.HELPER_TOKEN,
}
key = 'openapi:colg:real_time_gold_officical' key = 'openapi:colg:real_time_gold_officical'
list = ['1', '2', '3A', '3B', '4', '5', '6', '7'] list = ['1', '2', '3A', '3B', '4', '5', '6', '7']
try: try:
async def get_data(): async def get_data():
datas = [] datas = []
async def fetch_data(): async with aiohttp.ClientSession() as session:
async with aiohttp.ClientSession() as session: tasks = []
tasks = [] for i in list:
for i in list: payload_helper['serverNo'] = i
params['serverNo'] = i query = '&'.join(f'{k}={v}' for k, v in payload_helper.items())
query = '&'.join(f'{k}={v}' for k, v in params.items()) tasks.append(session.post(official_url, data=query, headers=headers, timeout=10))
tasks.append(session.post(official_url, data=query, headers=headers, timeout=10)) responses = await asyncio.gather(*tasks)
responses = await asyncio.gather(*tasks) for index, resp in enumerate(responses):
for index, resp in enumerate(responses): res = json.loads(await resp.read())
res = json.loads(await resp.read()) datas.append(
datas.append({ {
'updataTime': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), 'updataTime': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'region': f'跨{list[index]}', 'region': f'跨{list[index]}',
'godDealRank': [int(i.get('price')) for i in res['data']['godDealRank']], 'goldDealRank': [int(i.get('price')) for i in res['data']['godDealRank']],
'godTopDealRank': [int(i.get('price')) for i in res['data']['godTopDealRank']], 'goldTopDealRank': [int(i.get('price')) for i in res['data']['godTopDealRank']],
}) }
return datas )
return await fetch_data() return datas
except Exception as e: except Exception as e:
print(e) print(e)
return response(data=[]) return response(data=[])
+5 -5
View File
@@ -37,7 +37,7 @@ def replace_placeholders(template):
return pattern.sub(repl, template) return pattern.sub(repl, template)
@router.get("/skills/", operation_id="skillsList") @router.get("/skills/", operation_id="skills_list", response_model=SearchSkillResponse)
async def get_skills_list( async def get_skills_list(
redis: RedisDep, redis: RedisDep,
skillName: Annotated[str, Param(..., description="技能名称,支持模糊查询")] = None, skillName: Annotated[str, Param(..., description="技能名称,支持模糊查询")] = None,
@@ -69,7 +69,7 @@ async def get_skills_list(
return response(data=data) return response(data=data)
@router.get("/jobInfo/", operation_id="jobsInfo") @router.get("/jobInfo/", operation_id="jobs_info", response_model=JobInfoResponse)
async def get_jobs_info( async def get_jobs_info(
redis: RedisDep, redis: RedisDep,
jobName: Annotated[str, Query(..., description="职业名称,支持模糊查询")] = None, jobName: Annotated[str, Query(..., description="职业名称,支持模糊查询")] = None,
@@ -101,7 +101,7 @@ async def get_jobs_info(
return response(data=result) return response(data=result)
@router.get("/{jobId}/{jobGrowId}/skills/", operation_id="skillsByJob") @router.get("/{jobId}/{jobGrowId}/skills/", operation_id="skills_by_job",response_model=SkillsListResponse)
async def get_cn_skills_by_job( async def get_cn_skills_by_job(
redis: RedisDep, redis: RedisDep,
jobId: Annotated[str, Path(..., description="职业")], jobId: Annotated[str, Path(..., description="职业")],
@@ -125,7 +125,7 @@ async def get_cn_skills_by_job(
return response(data=skills_list) return response(data=skills_list)
@router.get("/{jobId}/{jobGrowId}/{skillId}/", operation_id="skillDetail") @router.get("/{jobId}/{jobGrowId}/{skillId}/", operation_id="skill_detail", response_model=SkillDetailResponse)
async def get_cn_skill_info( async def get_cn_skill_info(
redis: RedisDep, redis: RedisDep,
jobId: Annotated[str, Path(..., description="职业")], jobId: Annotated[str, Path(..., description="职业")],
@@ -192,7 +192,7 @@ async def get_cn_skill_info(
return response(data=skill_info) return response(data=skill_info)
@router.get("/skillData/summary/", operation_id="skillDataSummary") @router.get("/skillData/summary/", operation_id="skill_data_summary",response_model=SkillDataSummaryResponse)
async def get_skill_data_summary( async def get_skill_data_summary(
redis: RedisDep, redis: RedisDep,
job: Annotated[str, Param(..., description="职业")], job: Annotated[str, Param(..., description="职业")],
+3 -4
View File
@@ -1,7 +1,6 @@
from enum import Enum from enum import Enum
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from typing import Generic, TypeVar from typing import Generic, TypeVar
from pydantic.generics import GenericModel
from api.core.Response import Return as Response from api.core.Response import Return as Response
@@ -10,7 +9,7 @@ class RealTimeGold(BaseModel):
"""更新时间""" """更新时间"""
region: str = Field(..., description='跨区') region: str = Field(..., description='跨区')
"""跨区""" """跨区"""
goldPrice: float = Field(..., description='金价') goldPrice: float|str = Field(..., description='金价')
"""金价""" """金价"""
class DayGold(BaseModel): class DayGold(BaseModel):
@@ -33,7 +32,7 @@ class OfficialGold(BaseModel):
"""更新时间""" """更新时间"""
region: str = Field(..., description='跨区') region: str = Field(..., description='跨区')
"""跨区""" """跨区"""
godDealRank: list[float] = Field(..., description='金币寄售最低成交价排名前5的价格') goldDealRank: list[float|str] = Field(..., description='金币寄售最低成交价排名前5的价格')
"""金币寄售最低成交价排名前5的价格""" """金币寄售最低成交价排名前5的价格"""
godTopDealRank: list[float] = Field(..., description='金币寄售最高成交价排名前5的价格') goldTopDealRank: list[float|str] = Field(..., description='金币寄售最高成交价排名前5的价格')
"""金币寄售最高成交价排名前5的价格""" """金币寄售最高成交价排名前5的价格"""
-3
View File
@@ -1,10 +1,7 @@
from enum import Enum from enum import Enum
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from typing import Generic, TypeVar
from pydantic.generics import GenericModel
from api.core.Response import Return as Response from api.core.Response import Return as Response
class SkillInfo(BaseModel): class SkillInfo(BaseModel):
skillId: str = Field(..., description='技能ID') skillId: str = Field(..., description='技能ID')
"""技能ID""" """技能ID"""
+11 -3
View File
@@ -1,5 +1,12 @@
from pydantic_settings import BaseSettings import os
import sys
from pydantic_settings import BaseSettings, SettingsConfigDict
try:
os.chdir(os.path.dirname(sys.argv[0]))
except Exception:
pass
class Config(BaseSettings): class Config(BaseSettings):
DEBUG_MODE: bool = True DEBUG_MODE: bool = True
@@ -11,8 +18,9 @@ class Config(BaseSettings):
HELPER_TOKEN: str = '' HELPER_TOKEN: str = ''
HELPER_ID: str = '' HELPER_ID: str = ''
class Config: model_config = SettingsConfigDict(
env_file = ('config/.env', 'config/.env.dev', 'config/.env.prod') env_file=('config/.env', 'config/.env.dev', 'config/.env.prod')
)
config = Config() config = Config()
+1 -2
View File
@@ -1,9 +1,8 @@
# import os # import os
# import sys # import sys
from sqlalchemy import Column, Integer, Text, Float from sqlalchemy import Column, Integer, Text, Float
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine from sqlalchemy import create_engine
from sqlalchemy.orm import Session from sqlalchemy.orm import Session,declarative_base
Base = declarative_base() Base = declarative_base()
metadata = Base.metadata metadata = Base.metadata
+3 -6
View File
@@ -1,10 +1,10 @@
from fastmcp import FastMCP from fastmcp import FastMCP
from main import application from main import application
from fastmcp.server.openapi import MCPType, HTTPRoute from fastmcp.server.providers.openapi import MCPType
from config.main import config from config.main import config
def custom_route_mapper(route: HTTPRoute, mcp_type: MCPType) -> MCPType | None: def custom_route_mapper(route, mcp_type: MCPType) -> MCPType | None:
"""Advanced route type mapping.""" """Advanced route type mapping."""
# Convert all admin routes to tools regardless of HTTP method # Convert all admin routes to tools regardless of HTTP method
if "/open/" in route.path and route.method in ["GET"]: if "/open/" in route.path and route.method in ["GET"]:
@@ -16,10 +16,7 @@ mcp = FastMCP.from_fastapi(
app=application, app=application,
name="Dcalc_MCP", name="Dcalc_MCP",
instructions="DNF相关的MCP服务", instructions="DNF相关的MCP服务",
timeout=60,
host="0.0.0.0",
port=config.MCPPORT,
route_map_fn=custom_route_mapper, route_map_fn=custom_route_mapper,
) )
mcp.run(transport="streamable-http") mcp.run(transport="streamable-http", host="0.0.0.0", port=config.MCPPORT)
+1 -1
View File
@@ -7,7 +7,7 @@ requires-python = ">=3.10"
dependencies = [ dependencies = [
"aiohttp>=3.13.3", "aiohttp>=3.13.3",
"fastapi[all]>=0.115.11", "fastapi[all]>=0.115.11",
"fastmcp>=2.7.1", "fastmcp>=3.1.0",
"redis>=5.2.1", "redis>=5.2.1",
"requests>=2.32.3", "requests>=2.32.3",
"sqlalchemy>=2.0.38", "sqlalchemy>=2.0.38",
+891 -184
View File
File diff suppressed because it is too large Load Diff