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': '微博'} ] })