初始化提交

This commit is contained in:
2025-12-14 15:40:49 +08:00
commit 410b2f068d
72 changed files with 10460 additions and 0 deletions

View File

@@ -0,0 +1,244 @@
from models import db, User, Order, ApiCall, Transaction
from sqlalchemy import desc, func
from datetime import datetime, timedelta
class AdminService:
@staticmethod
def get_users(page=1, per_page=20, search=''):
"""获取用户列表"""
query = User.query
if search:
query = query.filter(
(User.email.contains(search)) | (User.username.contains(search))
)
# 分页查询
pagination = query.order_by(desc(User.created_at))\
.paginate(page=page, per_page=per_page, error_out=False)
return {
'users': [user.to_dict() for user in pagination.items],
'total': pagination.total,
'page': page,
'per_page': per_page,
'pages': pagination.pages
}, 200
@staticmethod
def get_user_detail(user_id):
"""获取用户详情"""
user = User.query.get(user_id)
if not user:
return {'error': '用户不存在'}, 404
# 统计信息
total_recharge = db.session.query(func.sum(Transaction.amount))\
.filter_by(user_id=user_id, type='recharge').scalar() or 0
total_consume = db.session.query(func.sum(Transaction.amount))\
.filter_by(user_id=user_id, type='consume').scalar() or 0
total_api_calls = ApiCall.query.filter_by(user_id=user_id).count()
return {
'user': user.to_dict(),
'stats': {
'total_recharge': total_recharge,
'total_consume': abs(total_consume),
'total_api_calls': total_api_calls
}
}, 200
@staticmethod
def toggle_user_status(admin_user_id, user_id):
"""启用/禁用用户"""
user = User.query.get(user_id)
if not user:
return {'error': '用户不存在'}, 404
# 检查是否尝试禁用其他管理员 (需要传入当前管理员ID)
if user.is_admin and user.id != admin_user_id:
return {'error': '不能禁用其他管理员'}, 403
user.is_active = not user.is_active
try:
db.session.commit()
return {
'message': f'用户已{"启用" if user.is_active else "禁用"}',
'user': user.to_dict()
}, 200
except Exception as e:
db.session.rollback()
return {'error': '操作失败'}, 500
@staticmethod
def adjust_balance(user_id, data):
"""调整用户余额"""
user = User.query.get(user_id)
if not user:
return {'error': '用户不存在'}, 404
amount = data.get('amount')
description = data.get('description', '管理员调整余额')
if amount is None or amount == 0:
return {'error': '金额不能为0'}, 400
try:
balance_before = user.balance
user.balance += amount
balance_after = user.balance
# 创建交易记录
transaction = Transaction(
user_id=user_id,
type='recharge' if amount > 0 else 'consume',
amount=amount,
balance_before=balance_before,
balance_after=balance_after,
description=description
)
db.session.add(transaction)
db.session.commit()
return {
'message': '余额调整成功',
'user': user.to_dict(),
'transaction': transaction.to_dict()
}, 200
except Exception as e:
db.session.rollback()
return {'error': '余额调整失败'}, 500
@staticmethod
def get_all_orders(page=1, per_page=20, status=None):
"""获取所有订单"""
query = Order.query
if status:
query = query.filter_by(status=status)
# 分页查询
pagination = query.order_by(desc(Order.created_at))\
.paginate(page=page, per_page=per_page, error_out=False)
return {
'orders': [order.to_dict() for order in pagination.items],
'total': pagination.total,
'page': page,
'per_page': per_page,
'pages': pagination.pages
}, 200
@staticmethod
def get_all_api_calls(page=1, per_page=20, status=None):
"""获取所有API调用记录"""
query = ApiCall.query
if status:
query = query.filter_by(status=status)
# 分页查询
pagination = query.order_by(desc(ApiCall.created_at))\
.paginate(page=page, per_page=per_page, error_out=False)
return {
'api_calls': [call.to_dict() for call in pagination.items],
'total': pagination.total,
'page': page,
'per_page': per_page,
'pages': pagination.pages
}, 200
@staticmethod
def get_overview_stats():
"""获取总览统计"""
# 用户统计
total_users = User.query.count()
active_users = User.query.filter_by(is_active=True).count()
# 订单统计
total_orders = Order.query.count()
paid_orders = Order.query.filter_by(status='paid').count()
total_revenue = db.session.query(func.sum(Order.amount))\
.filter_by(status='paid').scalar() or 0
# API调用统计
total_api_calls = ApiCall.query.count()
success_calls = ApiCall.query.filter_by(status='success').count()
failed_calls = ApiCall.query.filter_by(status='failed').count()
# 今日统计
today = datetime.utcnow().date()
today_start = datetime.combine(today, datetime.min.time())
today_users = User.query.filter(User.created_at >= today_start).count()
today_orders = Order.query.filter(Order.created_at >= today_start).count()
today_revenue = db.session.query(func.sum(Order.amount))\
.filter(Order.created_at >= today_start, Order.status == 'paid').scalar() or 0
today_api_calls = ApiCall.query.filter(ApiCall.created_at >= today_start).count()
return {
'total': {
'users': total_users,
'active_users': active_users,
'orders': total_orders,
'paid_orders': paid_orders,
'revenue': total_revenue,
'api_calls': total_api_calls,
'success_calls': success_calls,
'failed_calls': failed_calls
},
'today': {
'users': today_users,
'orders': today_orders,
'revenue': today_revenue,
'api_calls': today_api_calls
}
}, 200
@staticmethod
def get_chart_data(days=7):
"""获取图表数据"""
# 计算日期范围
end_date = datetime.utcnow().date()
start_date = end_date - timedelta(days=days-1)
chart_data = []
for i in range(days):
date = start_date + timedelta(days=i)
date_start = datetime.combine(date, datetime.min.time())
date_end = datetime.combine(date, datetime.max.time())
# 统计当天数据
users = User.query.filter(
User.created_at >= date_start,
User.created_at <= date_end
).count()
revenue = db.session.query(func.sum(Order.amount))\
.filter(
Order.created_at >= date_start,
Order.created_at <= date_end,
Order.status == 'paid'
).scalar() or 0
api_calls = ApiCall.query.filter(
ApiCall.created_at >= date_start,
ApiCall.created_at <= date_end
).count()
chart_data.append({
'date': date.isoformat(),
'users': users,
'revenue': float(revenue),
'api_calls': api_calls
})
return {
'chart_data': chart_data
}, 200

View File

@@ -0,0 +1,258 @@
from flask import current_app
from models import db, User, ApiCall, Transaction
from datetime import datetime
import requests
import json
import logging
from modelapiservice import get_model_service
logger = logging.getLogger(__name__)
class ApiProxyService:
@staticmethod
def deduct_balance(user_id, api_call_id, cost, model):
"""统一扣费逻辑"""
try:
user = User.query.get(user_id)
api_call = ApiCall.query.get(api_call_id)
if not user or not api_call:
return
# 刷新用户数据
db.session.refresh(user)
balance_before = user.balance
user.balance -= cost
balance_after = user.balance
api_call.status = 'success'
api_call.cost = cost
db.session.add(api_call)
transaction = Transaction(
user_id=user.id,
type='consume',
amount=-cost,
balance_before=balance_before,
balance_after=balance_after,
description=f'API调用 - {model}',
api_call_id=api_call.id
)
db.session.add(transaction)
db.session.commit()
logger.info(f"扣费成功: 用户 {user.id}, 消费 {cost}, 余额 {balance_before} -> {balance_after}")
except Exception as e:
logger.error(f'扣费失败: {e}')
db.session.rollback()
@staticmethod
def handle_api_request(user_id, data):
"""
通用 API 代理 (支持文生图和对话)
"""
user = User.query.get(user_id)
if not user:
return {'error': '用户不存在'}, 404
if not user.is_active:
return {'error': '账户已被禁用'}, 403
model = data.get('model')
messages = data.get('messages', [])
stream = data.get('stream', False)
# 验证必要字段
if not model:
return {'error': 'model字段不能为空'}, 400
if not messages or len(messages) == 0:
return {'error': 'messages不能为空'}, 400
# 获取模型服务并检查余额
try:
service = get_model_service(model)
except Exception as e:
return {'error': f'不支持的模型: {model}'}, 400
is_sufficient, estimated_cost, error_msg = service.check_balance(user.balance)
if not is_sufficient:
return {
'error': '余额不足',
'message': error_msg,
'required': estimated_cost,
'balance': user.balance
}, 402
prompt = messages[0].get('content', '') if messages else ''
if not prompt:
prompt = "Empty prompt"
# 创建API调用记录
api_call = ApiCall(
user_id=user_id,
api_type='chat_completion',
prompt=prompt[:500], # 截断
parameters=json.dumps({
'model': model,
'stream': stream
}),
status='processing',
cost=estimated_cost,
request_time=datetime.utcnow()
)
try:
db.session.add(api_call)
db.session.flush()
# 准备请求
api_url, api_key = service.get_api_config()
if not api_url or not api_key:
raise ValueError(f'模型 {model} API 配置未完成')
headers = {
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json'
}
payload = service.prepare_payload(data)
target_url = f'{api_url}/chat/completions'
logger.info(f'API 转发: {target_url}, User: {user.id}, Model: {model}')
response = requests.post(
target_url,
headers=headers,
json=payload,
stream=stream,
timeout=300
)
if response.status_code != 200:
error_msg = f'第三方 API 返回错误: {response.status_code}'
try:
error_detail = response.json()
error_msg += f' - {error_detail}'
except:
error_msg += f' - {response.text[:200]}'
api_call.status = 'failed'
api_call.error_message = error_msg
db.session.commit()
return {'error': 'API 调用失败', 'details': error_msg}, 502
# 处理响应
if stream:
# 流式响应处理
def generate():
final_usage = None
try:
for chunk in response.iter_content(chunk_size=1024):
if chunk:
if hasattr(service, 'parse_stream_usage'):
try:
text_chunk = chunk.decode('utf-8', errors='ignore')
usage = service.parse_stream_usage(text_chunk)
if usage:
final_usage = usage
except:
pass
yield chunk
# 计算最终费用
actual_cost = service.calculate_cost(final_usage, stream=True)
if actual_cost == 0 and estimated_cost > 0:
actual_cost = estimated_cost
with current_app.app_context():
ApiProxyService.deduct_balance(user.id, api_call.id, actual_cost, model)
except Exception as e:
logger.error(f'Stream error: {e}')
return generate(), 200 # Special return for stream
else:
result = response.json()
api_call.status = 'success'
api_call.response_time = datetime.utcnow()
# 计算费用
usage = result.get('usage')
final_cost = service.calculate_cost(usage, stream=False)
if final_cost == 0 and estimated_cost > 0:
final_cost = estimated_cost
# 简化响应格式
simplified_result = {
'success': True,
'api_call_id': api_call.id,
'cost': final_cost,
'model': model,
'content': ''
}
if 'choices' in result and len(result['choices']) > 0:
content = result['choices'][0].get('message', {}).get('content', '')
simplified_result['content'] = content
api_call.result_url = content[:500]
ApiProxyService.deduct_balance(user.id, api_call.id, final_cost, model)
return simplified_result, 200
except Exception as e:
logger.error(f'API 调用异常: {str(e)}', exc_info=True)
if api_call.id:
api_call.status = 'failed'
api_call.error_message = str(e)
db.session.commit()
return {'error': '服务异常', 'message': str(e)}, 500
@staticmethod
def get_models():
"""获取可用的模型列表"""
# 暂时返回硬编码的模型列表,后续可以从各 Service 聚合
return {
'object': 'list',
'data': [
{
'id': 'deepseek-chat',
'object': 'model',
'owned_by': 'deepseek',
'description': 'DeepSeek Chat V3'
},
{
'id': 'deepseek-reasoner',
'object': 'model',
'owned_by': 'deepseek',
'description': 'DeepSeek Reasoner (R1)'
},
# ... 其他模型 ...
]
}, 200
@staticmethod
def get_pricing():
"""获取价格信息"""
pricing = {
'text_to_image': {
'price': current_app.config.get('IMAGE_GENERATION_PRICE', 0),
'currency': 'CNY',
'unit': '每张图片'
}
}
return {
'pricing': pricing
}, 200
@staticmethod
def get_api_call(user_id, call_id):
"""获取API调用详情"""
api_call = ApiCall.query.filter_by(id=call_id, user_id=user_id).first()
if not api_call:
return {'error': 'API调用记录不存在'}, 404
return api_call.to_dict(), 200

View File

@@ -0,0 +1,165 @@
from models import db, User, APIKey
from datetime import datetime
class APIKeyService:
@staticmethod
def list_api_keys(user_id):
"""获取用户的所有 API Key"""
user = User.query.get(user_id)
if not user:
return {'error': '用户不存在'}, 404
keys = APIKey.query.filter_by(user_id=user_id).all()
return {
'total': len(keys),
'keys': [key.to_dict() for key in keys]
}, 200
@staticmethod
def create_api_key(user_id, data):
"""创建新的 API Key"""
user = User.query.get(user_id)
if not user:
return {'error': '用户不存在'}, 404
name = data.get('name', '').strip()
if not name:
return {'error': 'API Key 名称不能为空'}, 400
if len(name) > 100:
return {'error': 'API Key 名称长度不能超过100个字符'}, 400
# 生成 API Key
api_key = APIKey.generate_key()
# 创建数据库记录
new_key = APIKey(
user_id=user_id,
name=name,
api_key=api_key
)
try:
db.session.add(new_key)
db.session.commit()
return {
'message': 'API Key 创建成功',
'key': new_key.to_dict()
}, 201
except Exception as e:
db.session.rollback()
return {'error': '创建失败,请稍后重试'}, 500
@staticmethod
def get_api_key(user_id, key_id):
"""获取单个 API Key 详情"""
key = APIKey.query.filter_by(id=key_id, user_id=user_id).first()
if not key:
return {'error': 'API Key 不存在'}, 404
return key.to_dict(), 200
@staticmethod
def update_api_key(user_id, key_id, data):
"""更新 API Key 名称或状态"""
key = APIKey.query.filter_by(id=key_id, user_id=user_id).first()
if not key:
return {'error': 'API Key 不存在'}, 404
if 'name' in data:
name = data.get('name', '').strip()
if not name or len(name) > 100:
return {'error': 'API Key 名称无效'}, 400
key.name = name
if 'is_active' in data:
key.is_active = bool(data.get('is_active'))
try:
db.session.commit()
return {
'message': 'API Key 更新成功',
'key': key.to_dict()
}, 200
except Exception as e:
db.session.rollback()
return {'error': '更新失败,请稍后重试'}, 500
@staticmethod
def delete_api_key(user_id, key_id):
"""删除 API Key"""
key = APIKey.query.filter_by(id=key_id, user_id=user_id).first()
if not key:
return {'error': 'API Key 不存在'}, 404
try:
db.session.delete(key)
db.session.commit()
return {'message': 'API Key 已删除'}, 200
except Exception as e:
db.session.rollback()
return {'error': '删除失败,请稍后重试'}, 500
@staticmethod
def regenerate_api_key(user_id, key_id):
"""重置/轮换 API Key"""
key = APIKey.query.filter_by(id=key_id, user_id=user_id).first()
if not key:
return {'error': 'API Key 不存在'}, 404
# 生成新的 API Key
new_api_key = APIKey.generate_key()
key.api_key = new_api_key
try:
db.session.commit()
return {
'message': 'API Key 已重置',
'key': key.to_dict()
}, 200
except Exception as e:
db.session.rollback()
return {'error': '重置失败,请稍后重试'}, 500
@staticmethod
def authenticate_api_key(auth_header):
"""验证 API Key 并返回用户"""
if not auth_header:
return None, "缺少 Authorization 头"
parts = auth_header.split()
if parts[0].lower() != "bearer":
return None, "Authorization 头格式错误"
if len(parts) == 1:
return None, "无效的 Token"
api_key_str = parts[1]
# 查找 API Key
api_key = APIKey.query.filter_by(api_key=api_key_str).first()
if not api_key:
return None, "无效的 API Key"
if not api_key.is_active:
return None, "API Key 已被禁用"
# 更新最后使用时间
api_key.last_used_at = datetime.utcnow()
db.session.commit()
user = User.query.get(api_key.user_id)
if not user or not user.is_active:
return None, "账户不存在或已被禁用"
return user, None

View File

@@ -0,0 +1,290 @@
from flask import current_app, jsonify
from flask_mail import Message
from flask_jwt_extended import create_access_token, create_refresh_token
from models import db, User, VerificationCode
from email_validator import validate_email, EmailNotValidError
from datetime import datetime, timedelta
import re
class AuthService:
@staticmethod
def send_verification_email(email, code, purpose='register'):
"""发送验证码邮件"""
try:
subject = '验证码' if purpose == 'register' else '密码重置验证码'
body = f"""
您好!
感谢您使用 Nano Banana API 转售平台。
您的验证码是:{code}
此验证码有效期为10分钟请勿泄露给他人。
如果不是您本人操作,请忽略此邮件。
---
Nano Banana API 平台
"""
msg = Message(
subject=f'[Nano Banana] {subject}',
recipients=[email],
body=body
)
# 这里使用 Flask-Mail 发送
from extensions import mail
mail.send(msg)
return True
except Exception as e:
print(f'发送邮件失败: {str(e)}')
return False
@staticmethod
def send_verification_code(data):
"""发送验证码逻辑"""
email = data.get('email', '').strip().lower()
purpose = data.get('purpose', 'register')
if not email:
return {'error': '邮箱不能为空'}, 400
# 验证邮箱格式
try:
validate_email(email, check_deliverability=False)
except EmailNotValidError:
return {'error': '邮箱格式不正确'}, 400
# 如果是注册,检查邮箱是否已存在
if purpose == 'register':
user = User.query.filter_by(email=email).first()
if user:
# 如果用户已存在且已验证邮箱,则提示已注册
if user.email_verified:
return {'error': '该邮箱已被注册'}, 400
# 如果用户存在但未验证,可以继续发送验证码(重发)
else:
# 创建临时用户记录用于验证码关联
user = User(email=email, username=email.split('@')[0])
user.set_password('temp_pending_registration')
db.session.add(user)
db.session.commit()
else:
# 密码重置:检查用户是否存在
user = User.query.filter_by(email=email).first()
if not user:
return {'error': '用户不存在'}, 404
# 生成验证码
code = VerificationCode.generate_code()
# 删除旧的未使用验证码
VerificationCode.query.filter_by(
email=email,
purpose=purpose,
used=False
).delete()
# 创建新的验证码
verification = VerificationCode(
user_id=user.id,
email=email,
code=code,
purpose=purpose,
expired_at=datetime.utcnow() + timedelta(minutes=10)
)
db.session.add(verification)
db.session.commit()
# 发送邮件
send_success = AuthService.send_verification_email(email, code, purpose)
if send_success:
return {
'message': '验证码已发送到您的邮箱',
'email': email,
'purpose': purpose
}, 200
else:
return {'error': '邮件发送失败,请检查邮箱配置'}, 500
@staticmethod
def register(data):
"""用户注册逻辑"""
if not data or not data.get('email') or not data.get('password') or not data.get('code'):
return {'error': '邮箱、密码和验证码不能为空'}, 400
email = data.get('email').strip().lower()
password = data.get('password')
code = data.get('code')
username = data.get('username', '').strip()
try:
validate_email(email, check_deliverability=False)
except EmailNotValidError:
return {'error': '邮箱格式不正确'}, 400
if len(password) < 6:
return {'error': '密码长度至少为6位'}, 400
verification = VerificationCode.query.filter_by(
email=email,
code=code,
purpose='register',
used=False
).first()
if not verification:
return {'error': '验证码不存在或已过期'}, 400
if not verification.is_valid():
return {'error': '验证码已过期'}, 400
existing_user = User.query.filter_by(email=email).filter(
User.id != verification.user_id
).first()
if existing_user:
return {'error': '该邮箱已被注册'}, 400
user = User.query.get(verification.user_id)
user.username = username or email.split('@')[0]
user.set_password(password)
user.email_verified = True
user.email_verified_at = datetime.utcnow()
user.is_active = True
verification.used = True
try:
db.session.commit()
access_token = create_access_token(identity=user.id)
refresh_token = create_refresh_token(identity=user.id)
return {
'message': '注册成功',
'access_token': access_token,
'refresh_token': refresh_token,
'user': user.to_dict()
}, 201
except Exception as e:
db.session.rollback()
print(f'注册错误: {str(e)}')
return {'error': '注册失败,请稍后重试'}, 500
@staticmethod
def login(data):
"""用户登录逻辑"""
if not data or not data.get('email') or not data.get('password'):
return {'error': '邮箱和密码不能为空'}, 400
email = data.get('email').strip().lower()
password = data.get('password')
user = User.query.filter_by(email=email).first()
if not user or not user.check_password(password):
return {'error': '邮箱或密码错误'}, 401
if not user.is_active:
return {'error': '账户已被禁用'}, 403
# 如果用户未验证邮箱 (理论上注册流程保证了已验证,但为了兼容旧数据)
if not user.email_verified:
return {'error': '邮箱未验证,请先完成注册验证'}, 403
access_token = create_access_token(identity=user.id)
refresh_token = create_refresh_token(identity=user.id)
return {
'message': '登录成功',
'access_token': access_token,
'refresh_token': refresh_token,
'user': user.to_dict()
}, 200
@staticmethod
def change_password(user_id, data):
"""修改密码"""
user = User.query.get(user_id)
if not user:
return {'error': '用户不存在'}, 404
old_password = data.get('old_password')
new_password = data.get('new_password')
if not old_password or not new_password:
return {'error': '参数不能为空'}, 400
if not user.check_password(old_password):
return {'error': '原密码错误'}, 401
if len(new_password) < 6:
return {'error': '新密码长度至少为6位'}, 400
user.set_password(new_password)
db.session.commit()
return {'message': '密码修改成功'}, 200
@staticmethod
def reset_password(data):
"""重置密码"""
email = data.get('email', '').strip().lower()
code = data.get('code')
new_password = data.get('new_password')
if not email or not code or not new_password:
return {'error': '参数不能为空'}, 400
# 验证验证码
verification = VerificationCode.query.filter_by(
email=email,
code=code,
purpose='password_reset',
used=False
).first()
if not verification or not verification.is_valid():
return {'error': '验证码不存在或已过期'}, 400
user = User.query.get(verification.user_id)
if not user:
return {'error': '用户不存在'}, 404
if len(new_password) < 6:
return {'error': '密码长度至少为6位'}, 400
# 更新密码
user.set_password(new_password)
verification.used = True
db.session.commit()
return {'message': '密码重置成功'}, 200
@staticmethod
def verify_code(data):
"""验证验证码是否有效"""
email = data.get('email', '').strip().lower()
code = data.get('code')
purpose = data.get('purpose', 'register')
if not email or not code:
return {'error': '参数不能为空'}, 400
verification = VerificationCode.query.filter_by(
email=email,
code=code,
purpose=purpose,
used=False
).first()
if not verification:
return {'error': '验证码错误'}, 400
if not verification.is_valid():
return {'error': '验证码已过期'}, 400
return {'message': '验证码有效'}, 200

View File

@@ -0,0 +1,150 @@
from flask import current_app
from models import db, User, Order, Transaction
from datetime import datetime
import uuid
from sqlalchemy import desc
class OrderService:
@staticmethod
def _generate_order_no():
"""生成订单号"""
return f"ORD{datetime.utcnow().strftime('%Y%m%d%H%M%S')}{uuid.uuid4().hex[:8].upper()}"
@staticmethod
def create_order(user_id, data):
"""创建充值订单"""
user = User.query.get(user_id)
if not user:
return {'error': '用户不存在'}, 404
amount = data.get('amount')
payment_method = data.get('payment_method', 'alipay') # alipay, wechat
# 验证金额
if not amount or amount <= 0:
return {'error': '充值金额必须大于0'}, 400
# 验证支付方式
if payment_method not in ['alipay', 'wechat']:
return {'error': '不支持的支付方式'}, 400
# 创建订单
order = Order(
order_no=OrderService._generate_order_no(),
user_id=user_id,
amount=amount,
payment_method=payment_method,
status='pending'
)
try:
db.session.add(order)
db.session.commit()
# TODO: 调用支付接口
# 这里返回支付参数,前端跳转到支付页面
payment_params = {
'order_no': order.order_no,
'amount': order.amount,
'payment_method': payment_method,
# 实际项目中应返回支付宝或微信的支付参数
'qr_code': f'https://payment.example.com/qr/{order.order_no}',
'payment_url': f'https://payment.example.com/pay/{order.order_no}'
}
return {
'message': '订单创建成功',
'order': order.to_dict(),
'payment': payment_params
}, 201
except Exception as e:
db.session.rollback()
return {'error': '订单创建失败'}, 500
@staticmethod
def get_orders(user_id, page=1, per_page=20, status=None):
"""获取订单列表"""
query = Order.query.filter_by(user_id=user_id)
if status:
query = query.filter_by(status=status)
# 分页查询
pagination = query.order_by(desc(Order.created_at))\
.paginate(page=page, per_page=per_page, error_out=False)
return {
'orders': [order.to_dict() for order in pagination.items],
'total': pagination.total,
'page': page,
'per_page': per_page,
'pages': pagination.pages
}, 200
@staticmethod
def get_order(user_id, order_id):
"""获取订单详情"""
order = Order.query.filter_by(id=order_id, user_id=user_id).first()
if not order:
return {'error': '订单不存在'}, 404
return order.to_dict(), 200
@staticmethod
def alipay_callback(data):
"""支付宝支付回调(预留接口)"""
# TODO: 实现支付宝回调逻辑
return {'message': '处理成功'}, 200
@staticmethod
def wechat_callback(data):
"""微信支付回调(预留接口)"""
# TODO: 实现微信支付回调逻辑
return {'message': '处理成功'}, 200
@staticmethod
def payment_notify(order_no):
"""模拟支付通知(仅用于测试)"""
order = Order.query.filter_by(order_no=order_no).first()
if not order:
return {'error': '订单不存在'}, 404
if order.status != 'pending':
return {'error': '订单状态不正确'}, 400
try:
# 更新订单状态
order.status = 'paid'
order.paid_at = datetime.utcnow()
order.transaction_id = f"TXN{uuid.uuid4().hex[:16].upper()}"
# 增加用户余额
user = User.query.get(order.user_id)
balance_before = user.balance
user.balance += order.amount
balance_after = user.balance
# 创建交易记录
transaction = Transaction(
user_id=user.id,
type='recharge',
amount=order.amount,
balance_before=balance_before,
balance_after=balance_after,
description=f'充值 - 订单号: {order.order_no}',
order_id=order.id
)
db.session.add(transaction)
db.session.commit()
return {
'message': '支付成功',
'order': order.to_dict()
}, 200
except Exception as e:
db.session.rollback()
return {'error': '支付处理失败'}, 500

View File

@@ -0,0 +1,89 @@
from flask import request
from models import db, User, Transaction, ApiCall
from sqlalchemy import desc
class UserService:
@staticmethod
def get_profile(user_id):
user = User.query.get(user_id)
if not user:
return {'error': '用户不存在'}, 404
return user.to_dict(), 200
@staticmethod
def update_profile(user_id, data):
user = User.query.get(user_id)
if not user:
return {'error': '用户不存在'}, 404
if 'username' in data:
user.username = data['username'].strip()
try:
db.session.commit()
return {
'message': '资料更新成功',
'user': user.to_dict()
}, 200
except Exception as e:
db.session.rollback()
return {'error': '资料更新失败'}, 500
@staticmethod
def get_balance(user_id):
user = User.query.get(user_id)
if not user:
return {'error': '用户不存在'}, 404
return {
'balance': user.balance,
'user_id': user.id
}, 200
@staticmethod
def get_transactions(user_id, page, per_page):
pagination = Transaction.query.filter_by(user_id=user_id)\
.order_by(desc(Transaction.created_at))\
.paginate(page=page, per_page=per_page, error_out=False)
return {
'transactions': [t.to_dict() for t in pagination.items],
'total': pagination.total,
'page': page,
'per_page': per_page,
'pages': pagination.pages
}, 200
@staticmethod
def get_api_calls(user_id, page, per_page):
pagination = ApiCall.query.filter_by(user_id=user_id)\
.order_by(desc(ApiCall.created_at))\
.paginate(page=page, per_page=per_page, error_out=False)
return {
'api_calls': [call.to_dict() for call in pagination.items],
'total': pagination.total,
'page': page,
'per_page': per_page,
'pages': pagination.pages
}, 200
@staticmethod
def get_stats(user_id):
total_calls = ApiCall.query.filter_by(user_id=user_id).count()
success_calls = ApiCall.query.filter_by(user_id=user_id, status='success').count()
failed_calls = ApiCall.query.filter_by(user_id=user_id, status='failed').count()
total_cost = db.session.query(db.func.sum(Transaction.amount))\
.filter_by(user_id=user_id, type='consume').scalar() or 0
total_recharge = db.session.query(db.func.sum(Transaction.amount))\
.filter_by(user_id=user_id, type='recharge').scalar() or 0
return {
'total_calls': total_calls,
'success_calls': success_calls,
'failed_calls': failed_calls,
'total_cost': abs(total_cost),
'total_recharge': total_recharge
}, 200

View File

@@ -0,0 +1,174 @@
from flask import current_app
from models import db, User, ApiCall
from datetime import datetime
import requests
import json
import logging
from modelapiservice import get_model_service
from services.api_proxy_service import ApiProxyService
logger = logging.getLogger(__name__)
class V1Service:
@staticmethod
def chat_completions(user, data):
"""
OpenAI 兼容的 Chat Completions 接口逻辑
"""
model = data.get('model')
messages = data.get('messages', [])
stream = data.get('stream', False)
if not model:
return {'error': {'message': '缺少 model 参数', 'type': 'invalid_request_error', 'code': 400}}, 400
if not messages:
return {'error': {'message': '缺少 messages 参数', 'type': 'invalid_request_error', 'code': 400}}, 400
# 获取模型服务并检查余额
try:
service = get_model_service(model)
except Exception as e:
return {'error': {'message': f'不支持的模型: {model}', 'type': 'invalid_request_error', 'code': 400}}, 400
is_sufficient, estimated_cost, error_msg = service.check_balance(user.balance)
if not is_sufficient:
return {
'error': {
'message': error_msg,
'type': 'insufficient_quota',
'code': 402
}
}, 402
prompt = messages[-1].get('content', '') if messages else 'Empty prompt'
if len(prompt) > 500:
prompt = prompt[:500] + '...'
api_call = ApiCall(
user_id=user.id,
api_type='chat_completion',
prompt=prompt,
parameters=json.dumps({
'model': model,
'stream': stream
}),
status='processing',
cost=estimated_cost, # 暂时记录预估/固定费用
request_time=datetime.utcnow()
)
try:
db.session.add(api_call)
db.session.flush()
# 准备请求
api_url, api_key = service.get_api_config()
if not api_url or not api_key:
raise ValueError(f'模型 {model} API 配置未完成')
headers = {
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json'
}
payload = service.prepare_payload(data)
target_url = f'{api_url}/chat/completions'
logger.info(f'API 转发: {target_url}, User: {user.id}, Model: {model}')
response = requests.post(
target_url,
headers=headers,
json=payload,
stream=stream,
timeout=300
)
if response.status_code != 200:
error_msg = f'Upstream Error: {response.status_code}'
try:
error_detail = response.json()
error_msg += f' - {error_detail}'
except:
error_msg += f' - {response.text[:200]}'
api_call.status = 'failed'
api_call.error_message = error_msg
db.session.commit()
return {
'error': {
'message': error_msg,
'type': 'upstream_error',
'code': 502
}
}, 502
# 处理响应
if stream:
# 流式响应处理
def generate():
final_usage = None
try:
for chunk in response.iter_content(chunk_size=1024):
if chunk:
if hasattr(service, 'parse_stream_usage'):
try:
text_chunk = chunk.decode('utf-8', errors='ignore')
usage = service.parse_stream_usage(text_chunk)
if usage:
final_usage = usage
except:
pass
yield chunk
# 计算最终费用
actual_cost = service.calculate_cost(final_usage, stream=True)
if actual_cost == 0 and estimated_cost > 0:
actual_cost = estimated_cost
# 扣费
with current_app.app_context():
ApiProxyService.deduct_balance(user.id, api_call.id, actual_cost, model)
except Exception as e:
logger.error(f'Stream error: {e}')
return generate(), 200
else:
# 普通响应
result = response.json()
api_call.status = 'success'
api_call.response_time = datetime.utcnow()
# 计算费用
usage = result.get('usage')
final_cost = service.calculate_cost(usage, stream=False)
if final_cost == 0 and estimated_cost > 0:
final_cost = estimated_cost
# 简化响应格式
simplified_result = {
'model': model,
'content': '',
'cost': final_cost
}
if 'choices' in result and len(result['choices']) > 0:
content = result['choices'][0].get('message', {}).get('content', '')
simplified_result['content'] = content
# 同时更新 api_call 记录
api_call.result_url = content[:500]
ApiProxyService.deduct_balance(user.id, api_call.id, final_cost, model)
return simplified_result, 200
except Exception as e:
logger.error(f'API Error: {e}', exc_info=True)
if api_call.id:
api_call.status = 'failed'
api_call.error_message = str(e)
db.session.commit()
return {'error': {'message': 'Internal Server Error', 'type': 'server_error', 'code': 500}}, 500