无法查找到HTML模板,我的程序如下from flask import Flask, render_template, request, jsonify, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
from werkzeug.security import generate_password_hash, check_password_hash
import requests
import json
import os
import uuid
import redis
import time
from datetime import datetime, UTC
from functools import wraps
from dotenv import load_dotenv
# 加载环境变量
load_dotenv()
base_dir = os.path.abspath(os.path.dirname(__file__))
app = Flask(__name__,
template_folder=os.path.join(base_dir, 'templates'),
static_folder=os.path.join(base_dir, 'static'))
print(f"当前文件位置: {os.path.abspath(__file__)}")
print(f"项目根目录: {base_dir}")
print(f"模板路径: {app.template_folder}")
print(f"模板目录内容: {os.listdir(app.template_folder)}")
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'supersecretkey')
app.config[
'SQLALCHEMY_DATABASE_URI'] = f"mysql+mysqlconnector://{os.getenv('DB_USER')}:{os.getenv('DB_PASSWORD')}@{os.getenv('DB_HOST')}/{os.getenv('DB_NAME')}"
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['COZE_API_KEY'] = os.getenv('COZE_API_KEY')
app.config['COZE_BOT_ID'] = os.getenv('COZE_BOT_ID')
app.config['COZE_API_URL'] = "https://api.coze.cn/v3/chat"
app.config['REDIS_URL'] = os.getenv('REDIS_URL', 'redis://localhost:6379/0')
app.config['TOKEN_PRICE'] = 0.01 # 每个token的价格(美元)
# 初始化扩展
db = SQLAlchemy(app)
login_manager = LoginManager(app)
login_manager.login_view = 'login'
# 初始化Redis
redis_client = redis.Redis.from_url(app.config['REDIS_URL'])
# 数据库模型
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(50), unique=True, nullable=False)
password = db.Column(db.String(255), nullable=False)
email = db.Column(db.String(100), unique=True, nullable=False)
is_admin = db.Column(db.Boolean, default=False)
token_balance = db.Column(db.Integer, default=0)
created_at = db.Column(db.DateTime, default=lambda: datetime.now(UTC))
api_keys = db.relationship('APIKey', backref='user', lazy=True)
recharges = db.relationship('Recharge', backref='user', foreign_keys='Recharge.user_id', lazy=True)
messages = db.relationship('MessageCache', backref='user', lazy=True)
class APIKey(db.Model):
__tablename__ = 'api_keys'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
api_key = db.Column(db.String(255), unique=True, nullable=False)
is_active = db.Column(db.Boolean, default=True)
created_at = db.Column(db.DateTime, default=lambda: datetime.now(UTC))
class Recharge(db.Model):
__tablename__ = 'recharges'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
admin_id = db.Column(db.Integer, db.ForeignKey('users.id'))
amount = db.Column(db.Integer, nullable=False)
status = db.Column(db.String(20), default='pending')
created_at = db.Column(db.DateTime, default=lambda: datetime.now(UTC))
processed_at = db.Column(db.DateTime)
# 明确指定关系
admin = db.relationship('User', foreign_keys=[admin_id])
class MessageCache(db.Model):
__tablename__ = 'message_cache'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
request = db.Column(db.Text, nullable=False)
response = db.Column(db.Text, nullable=False)
tokens_used = db.Column(db.Integer, nullable=False)
created_at = db.Column(db.DateTime, default=lambda: datetime.now(UTC))
# Flask-Login 用户加载器
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
# 认证装饰器
def admin_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if not current_user.is_admin:
return redirect(url_for('dashboard'))
return f(*args, **kwargs)
return decorated_function
# 工具函数
def generate_api_key():
return str(uuid.uuid4()).replace('-', '')
def format_to_openai(coze_response):
"""将Coze API响应转换为OpenAI格式"""
choices = []
if 'messages' in coze_response:
for msg in coze_response['messages']:
if msg['role'] == 'assistant':
choices.append({
"index": 0,
"message": {
"role": "assistant",
"content": msg.get('content', '')
},
"finish_reason": "stop"
})
usage = coze_response.get('usage', {})
return {
"id": f"chatcmpl-{int(time.time())}",
"object": "chat.completion",
"created": int(time.time()),
"model": "coze-bot",
"choices": choices,
"usage": {
"prompt_tokens": usage.get('prompt_tokens', 0),
"completion_tokens": usage.get('completion_tokens', 0),
"total_tokens": usage.get('total_tokens', 0)
}
}
def deduct_tokens(user_id, amount):
"""扣除用户token"""
user = User.query.get(user_id)
if not user or user.token_balance < amount:
return False
user.token_balance -= amount
db.session.commit()
return True
def add_tokens(user_id, amount):
"""增加用户token"""
user = User.query.get(user_id)
if not user:
return False
user.token_balance += amount
db.session.commit()
return True
# 路由定义
@app.route('/')
def index():
template_path = os.path.join(app.template_folder, 'index.html')
if not os.path.exists(template_path):
return f"模板文件不存在!路径: {template_path}", 500
try:
return render_template('index.html')
except Exception as e:
return f"渲染错误: {str(e)}", 500
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
user = User.query.filter_by(username=username).first()
if user and check_password_hash(user.password, password):
login_user(user)
return redirect(url_for('dashboard'))
return render_template('login.html', error='Invalid username or password')
return render_template('login.html')
@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
username = request.form['username']
email = request.form['email']
password = generate_password_hash(request.form['password'])
admin_code = request.form.get('admin_code', '')
# 检查用户名和邮箱是否已存在
if User.query.filter_by(username=username).first():
return render_template('register.html', error='Username already exists')
if User.query.filter_by(email=email).first():
return render_template('register.html', error='Email already exists')
is_admin = admin_code == '789456123'
new_user = User(
username=username,
email=email,
password=password,
is_admin=is_admin
)
db.session.add(new_user)
db.session.commit()
login_user(new_user)
return redirect(url_for('dashboard'))
return render_template('register.html')
@app.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('index'))
@app.route('/dashboard')
@login_required
def dashboard():
return render_template('dashboard.html', user=current_user)
@app.route('/admin/dashboard')
@login_required
@admin_required
def admin_dashboard():
users = User.query.all()
pending_recharges = Recharge.query.filter_by(status='pending').all()
return render_template('admin_dashboard.html',
users=users,
recharges=pending_recharges)
@app.route('/generate-api-key', methods=['POST'])
@login_required
def generate_api_key_route():
new_key = APIKey(
user_id=current_user.id,
api_key=generate_api_key()
)
db.session.add(new_key)
db.session.commit()
return redirect(url_for('api_keys'))
@app.route('/api-keys')
@login_required
def api_keys():
return render_template('api_keys.html', api_keys=current_user.api_keys)
@app.route('/toggle-api-key/<int:key_id>', methods=['POST'])
@login_required
def toggle_api_key(key_id):
api_key = APIKey.query.get_or_404(key_id)
if api_key.user_id != current_user.id and not current_user.is_admin:
return jsonify({"error": "Unauthorized"}), 403
api_key.is_active = not api_key.is_active
db.session.commit()
return redirect(url_for('api_keys'))
@app.route('/recharge', methods=['POST'])
@login_required
def recharge():
try:
amount = int(request.form['amount'])
except ValueError:
return render_template('dashboard.html', user=current_user, error='Invalid amount')
if amount <= 0:
return render_template('dashboard.html', user=current_user, error='Amount must be positive')
new_recharge = Recharge(
user_id=current_user.id,
amount=amount
)
db.session.add(new_recharge)
db.session.commit()
return render_template('dashboard.html', user=current_user, success='Recharge request submitted')
@app.route('/admin/approve-recharge/<int:recharge_id>', methods=['POST'])
@login_required
@admin_required
def approve_recharge(recharge_id):
recharge = Recharge.query.get_or_404(recharge_id)
if recharge.status != 'pending':
return jsonify({"error": "Recharge already processed"}), 400
recharge.status = 'approved'
recharge.processed_at = lambda: datetime.now(UTC)
recharge.admin_id = current_user.id
add_tokens(recharge.user_id, recharge.amount)
db.session.commit()
return redirect(url_for('admin_dashboard'))
@app.route('/admin/reject-recharge/<int:recharge_id>', methods=['POST'])
@login_required
@admin_required
def reject_recharge(recharge_id):
recharge = Recharge.query.get_or_404(recharge_id)
if recharge.status != 'pending':
return jsonify({"error": "Recharge already processed"}), 400
recharge.status = 'rejected'
recharge.processed_at = lambda: datetime.now(UTC)
recharge.admin_id = current_user.id
db.session.commit()
return redirect(url_for('admin_dashboard'))
@app.route('/admin/update-tokens/<int:user_id>', methods=['POST'])
@login_required
@admin_required
def update_tokens(user_id):
user = User.query.get_or_404(user_id)
try:
amount = int(request.form['amount'])
except ValueError:
return jsonify({"error": "Invalid amount"}), 400
user.token_balance = amount
db.session.commit()
return redirect(url_for('admin_dashboard'))
@app.route('/admin/toggle-user/<int:user_id>', methods=['POST'])
@login_required
@admin_required
def toggle_user(user_id):
user = User.query.get_or_404(user_id)
# 切换所有API密钥状态
for api_key in user.api_keys:
api_key.is_active = not api_key.is_active
db.session.commit()
return redirect(url_for('admin_dashboard'))
# API端点
@app.route('/v1/chat/completions', methods=['POST'])
def chat_completion():
# 验证API密钥
auth_header = request.headers.get('Authorization')
if not auth_header or not auth_header.startswith('Bearer '):
return jsonify({"error": "Missing API key"}), 401
api_key = auth_header[7:] # 移除"Bearer "
api_key_obj = APIKey.query.filter_by(api_key=api_key).first()
if not api_key_obj or not api_key_obj.is_active:
return jsonify({"error": "Invalid API key"}), 401
user = api_key_obj.user
# 检查token余额
if user.token_balance <= 0:
return jsonify({"error": "Insufficient token balance"}), 403
# 尝试从Redis缓存获取响应
cache_key = f"request:{api_key}:{hash(str(request.json))}"
cached_response = redis_client.get(cache_key)
if cached_response:
return jsonify(json.loads(cached_response))
# 处理图片和文本
messages = request.json.get('messages', [])
coze_messages = []
for msg in messages:
if 'content' in msg:
# 简单判断是否为图片(实际应用中可能需要更复杂的逻辑)
content_type = 'image' if isinstance(msg['content'], dict) or msg['content'].startswith(
'data:image') else 'text'
coze_messages.append({
"role": msg['role'],
"content": msg['content'],
"content_type": content_type
})
# 构建Coze API请求
coze_data = {
"bot_id": app.config['COZE_BOT_ID'],
"user_id": str(user.id),
"stream": False,
"auto_save_history": True,
"additional_messages": coze_messages
}
headers = {
'Authorization': f'Bearer {app.config["COZE_API_KEY"]}',
'Content-Type': 'application/json'
}
# 发送请求到Coze API
try:
response = requests.post(
app.config['COZE_API_URL'],
headers=headers,
json=coze_data,
timeout=30
)
response.raise_for_status()
coze_response = response.json()
except requests.exceptions.RequestException as e:
app.logger.error(f"Coze API request failed: {str(e)}")
return jsonify({"error": "Failed to communicate with Coze API"}), 500
except json.JSONDecodeError:
app.logger.error("Failed to parse Coze API response")
return jsonify({"error": "Invalid response from Coze API"}), 500
# 处理token消耗
tokens_used = coze_response.get('usage', {}).get('total_tokens', 100) # 默认100
# 扣除token
if not deduct_tokens(user.id, tokens_used):
return jsonify({"error": "Failed to deduct tokens"}), 500
# 转换为OpenAI格式
openai_response = format_to_openai(coze_response)
# 缓存到Redis(1小时)
redis_client.setex(cache_key, 3600, json.dumps(openai_response))
# 保存到数据库
new_cache = MessageCache(
user_id=user.id,
request=json.dumps(request.json),
response=json.dumps(openai_response),
tokens_used=tokens_used
)
db.session.add(new_cache)
db.session.commit()
return jsonify(openai_response)
# 初始化数据库
@app.before_first_request
def create_tables():
db.create_all()
# 创建初始管理员账户(如果不存在)
admin_username = os.getenv('ADMIN_USERNAME', 'admin')
admin_email = os.getenv('ADMIN_EMAIL', 'admin@example.com')
admin_password = os.getenv('ADMIN_PASSWORD', 'adminpassword')
if not User.query.filter_by(username=admin_username).first():
admin = User(
username=admin_username,
email=admin_email,
password=generate_password_hash(admin_password),
is_admin=True
)
db.session.add(admin)
db.session.commit()
# 错误处理
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404
@app.errorhandler(500)
def internal_server_error(e):
return render_template('500.html'), 500
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
最新发布