feat: 新增平台
This commit is contained in:
104
utils/api_auth.py
Normal file
104
utils/api_auth.py
Normal file
@@ -0,0 +1,104 @@
|
||||
from functools import wraps
|
||||
from flask import request, jsonify
|
||||
from models import db, UserApiKey, ApiKeyDailyStat
|
||||
from datetime import datetime, date
|
||||
|
||||
|
||||
def get_api_key_from_request():
|
||||
"""从请求中获取 API Key"""
|
||||
# 优先从 Header 获取
|
||||
api_key = request.headers.get('X-API-Key')
|
||||
if api_key:
|
||||
return api_key
|
||||
|
||||
# 其次从 Authorization Bearer 获取
|
||||
auth_header = request.headers.get('Authorization')
|
||||
if auth_header and auth_header.startswith('Bearer '):
|
||||
return auth_header[7:]
|
||||
|
||||
# 最后从查询参数获取
|
||||
return request.args.get('api_key')
|
||||
|
||||
|
||||
def validate_api_key(api_key):
|
||||
"""
|
||||
验证 API Key
|
||||
返回: (is_valid, key_obj_or_error_msg)
|
||||
"""
|
||||
if not api_key:
|
||||
return False, 'API Key 不能为空'
|
||||
|
||||
key_obj = UserApiKey.query.filter_by(api_key=api_key).first()
|
||||
|
||||
if not key_obj:
|
||||
return False, 'API Key 无效'
|
||||
|
||||
if not key_obj.is_active:
|
||||
return False, 'API Key 已被禁用'
|
||||
|
||||
if not key_obj.user.is_active:
|
||||
return False, '用户账号已被禁用'
|
||||
|
||||
# 检查每日限额
|
||||
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 False, f'已达到每日调用限额({key_obj.daily_limit}次)'
|
||||
|
||||
return True, key_obj
|
||||
|
||||
|
||||
def record_api_call(key_obj, ip_address, success=True):
|
||||
"""记录 API 调用"""
|
||||
# 更新 Key 统计
|
||||
key_obj.total_calls += 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 += 1
|
||||
if success:
|
||||
today_stat.success_count += 1
|
||||
else:
|
||||
today_stat.fail_count += 1
|
||||
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def api_key_required(f):
|
||||
"""API Key 鉴权装饰器"""
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
api_key = get_api_key_from_request()
|
||||
|
||||
is_valid, result = validate_api_key(api_key)
|
||||
|
||||
if not is_valid:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': {
|
||||
'code': 'UNAUTHORIZED',
|
||||
'message': result
|
||||
}
|
||||
}), 401
|
||||
|
||||
# 将 key 对象传递给视图函数
|
||||
request.api_key_obj = result
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return decorated_function
|
||||
Reference in New Issue
Block a user