feat: 新增平台
This commit is contained in:
212
routes/api_v1.py
Normal file
212
routes/api_v1.py
Normal file
@@ -0,0 +1,212 @@
|
||||
from flask import Blueprint, request, jsonify
|
||||
from models import ParserAPI, ParseLog, UserApiKey, ApiKeyDailyStat, db
|
||||
from parsers.factory import ParserFactory
|
||||
from utils.security import get_client_ip
|
||||
from datetime import datetime, date
|
||||
import time
|
||||
|
||||
api_v1_bp = Blueprint('api_v1', __name__, url_prefix='/api/v1')
|
||||
|
||||
|
||||
def validate_and_get_key(api_key):
|
||||
"""验证 API Key 并返回 key 对象"""
|
||||
if not api_key:
|
||||
return None, 'API Key 不能为空'
|
||||
|
||||
key_obj = UserApiKey.query.filter_by(api_key=api_key).first()
|
||||
if not key_obj:
|
||||
return None, 'API Key 无效'
|
||||
if not key_obj.is_active:
|
||||
return None, 'API Key 已被禁用'
|
||||
if not key_obj.user.is_active:
|
||||
return None, '用户账号已被禁用'
|
||||
|
||||
# 检查每日限额
|
||||
today_stat = ApiKeyDailyStat.query.filter_by(
|
||||
api_key_id=key_obj.id,
|
||||
date=date.today()
|
||||
).first()
|
||||
if today_stat and today_stat.call_count >= key_obj.daily_limit:
|
||||
return None, f'已达到每日调用限额({key_obj.daily_limit}次)'
|
||||
|
||||
return key_obj, None
|
||||
|
||||
|
||||
def record_api_call(key_obj, ip_address, success=True):
|
||||
"""记录 API 调用"""
|
||||
key_obj.total_calls = (key_obj.total_calls or 0) + 1
|
||||
key_obj.last_used_at = datetime.utcnow()
|
||||
key_obj.last_used_ip = ip_address
|
||||
|
||||
today_stat = ApiKeyDailyStat.query.filter_by(
|
||||
api_key_id=key_obj.id,
|
||||
date=date.today()
|
||||
).first()
|
||||
|
||||
if not today_stat:
|
||||
today_stat = ApiKeyDailyStat(api_key_id=key_obj.id, date=date.today())
|
||||
db.session.add(today_stat)
|
||||
|
||||
today_stat.call_count = (today_stat.call_count or 0) + 1
|
||||
if success:
|
||||
today_stat.success_count = (today_stat.success_count or 0) + 1
|
||||
else:
|
||||
today_stat.fail_count = (today_stat.fail_count or 0) + 1
|
||||
|
||||
|
||||
@api_v1_bp.route('/parse', methods=['GET'])
|
||||
def parse_video():
|
||||
"""
|
||||
对外解析API - 简化版
|
||||
|
||||
请求方式: GET
|
||||
参数:
|
||||
key: API Key
|
||||
url: 视频链接
|
||||
|
||||
示例: /api/v1/parse?key=sk_xxx&url=https://v.douyin.com/xxx
|
||||
|
||||
返回:
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "解析成功",
|
||||
"data": {
|
||||
"cover": "封面URL",
|
||||
"title": "标题",
|
||||
"description": "简介",
|
||||
"author": "作者",
|
||||
"video_url": "无水印视频链接"
|
||||
}
|
||||
}
|
||||
"""
|
||||
# 获取参数
|
||||
api_key = request.args.get('key')
|
||||
video_url = request.args.get('url')
|
||||
|
||||
# 验证 API Key
|
||||
key_obj, error = validate_and_get_key(api_key)
|
||||
if error:
|
||||
return jsonify({'code': 401, 'msg': error})
|
||||
|
||||
# 验证 URL
|
||||
if not video_url:
|
||||
return jsonify({'code': 400, 'msg': '请提供视频链接'})
|
||||
|
||||
# 检测平台
|
||||
try:
|
||||
platform = ParserFactory.detect_platform(video_url)
|
||||
except ValueError as e:
|
||||
return jsonify({'code': 400, 'msg': str(e)})
|
||||
|
||||
# 展开短链接
|
||||
video_url = ParserFactory.expand_short_url(video_url)
|
||||
|
||||
# 获取客户端IP
|
||||
ip_address = get_client_ip(request)
|
||||
start_time = time.time()
|
||||
|
||||
# 获取该平台所有可用的API
|
||||
available_apis = ParserAPI.query.filter_by(
|
||||
platform=platform.lower(),
|
||||
is_enabled=True
|
||||
).all()
|
||||
|
||||
if not available_apis:
|
||||
return jsonify({'code': 503, 'msg': f'没有可用的{platform}解析接口'})
|
||||
|
||||
last_error = None
|
||||
user_id = key_obj.user_id
|
||||
|
||||
# 尝试所有可用的API(failover机制)
|
||||
for api_config in available_apis:
|
||||
try:
|
||||
parser = ParserFactory.create_parser(api_config)
|
||||
result = parser.parse(video_url)
|
||||
|
||||
# 验证解析结果,video_url 不能为空
|
||||
if not result.get('video_url'):
|
||||
raise Exception('未能获取到视频链接')
|
||||
|
||||
response_time = int((time.time() - start_time) * 1000)
|
||||
|
||||
# 记录成功日志
|
||||
log = ParseLog(
|
||||
user_id=user_id,
|
||||
ip_address=ip_address,
|
||||
platform=platform,
|
||||
video_url=video_url,
|
||||
parser_api_id=api_config.id,
|
||||
status='success',
|
||||
response_time=response_time
|
||||
)
|
||||
db.session.add(log)
|
||||
|
||||
# 更新API统计
|
||||
api_config.total_calls = (api_config.total_calls or 0) + 1
|
||||
api_config.success_calls = (api_config.success_calls or 0) + 1
|
||||
avg_time = api_config.avg_response_time or 0
|
||||
api_config.avg_response_time = int(
|
||||
(avg_time * (api_config.total_calls - 1) + response_time) / api_config.total_calls
|
||||
)
|
||||
api_config.fail_count = 0
|
||||
|
||||
# 记录 API Key 调用
|
||||
record_api_call(key_obj, ip_address, success=True)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'code': 200,
|
||||
'msg': '解析成功',
|
||||
'data': {
|
||||
'cover': result.get('cover', ''),
|
||||
'title': result.get('title', ''),
|
||||
'description': result.get('description', ''),
|
||||
'author': result.get('author', ''),
|
||||
'video_url': result.get('video_url', '')
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
last_error = str(e)
|
||||
api_config.total_calls = (api_config.total_calls or 0) + 1
|
||||
api_config.fail_count = (api_config.fail_count or 0) + 1
|
||||
db.session.commit()
|
||||
continue
|
||||
|
||||
# 所有API都失败
|
||||
response_time = int((time.time() - start_time) * 1000)
|
||||
|
||||
log = ParseLog(
|
||||
user_id=user_id,
|
||||
ip_address=ip_address,
|
||||
platform=platform,
|
||||
video_url=video_url,
|
||||
status='failed',
|
||||
error_message=last_error or '所有接口都失败',
|
||||
response_time=response_time
|
||||
)
|
||||
db.session.add(log)
|
||||
record_api_call(key_obj, ip_address, success=False)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'code': 500,
|
||||
'msg': last_error or '解析失败,请稍后重试'
|
||||
})
|
||||
|
||||
|
||||
@api_v1_bp.route('/platforms', methods=['GET'])
|
||||
def get_platforms():
|
||||
"""获取支持的平台列表"""
|
||||
return jsonify({
|
||||
'code': 200,
|
||||
'msg': '获取成功',
|
||||
'data': [
|
||||
{'name': 'douyin', 'display_name': '抖音'},
|
||||
{'name': 'tiktok', 'display_name': 'TikTok'},
|
||||
{'name': 'bilibili', 'display_name': '哔哩哔哩'},
|
||||
{'name': 'kuaishou', 'display_name': '快手'},
|
||||
{'name': 'pipixia', 'display_name': '皮皮虾'},
|
||||
{'name': 'weibo', 'display_name': '微博'}
|
||||
]
|
||||
})
|
||||
Reference in New Issue
Block a user