├── flasks ├── static │ ├── css │ │ └── style.css │ ├── js │ │ └── main.js │ ├── favicon.png │ └── img │ │ ├── gpt.png │ │ └── claude.png ├── __pycache__ │ ├── utils.cpython-311.pyc │ ├── __init__.cpython-311.pyc │ └── models.cpython-311.pyc ├── blueprints │ ├── __pycache__ │ │ ├── auth.cpython-311.pyc │ │ ├── chat.cpython-311.pyc │ │ ├── main.cpython-311.pyc │ │ ├── user.cpython-311.pyc │ │ ├── admin.cpython-311.pyc │ │ ├── claude.cpython-311.pyc │ │ └── __init__.cpython-311.pyc │ ├── __init__.py │ ├── user.py │ ├── claude.py │ ├── chat.py │ ├── main.py │ ├── auth.py │ └── admin.py ├── templates │ ├── error │ │ ├── 404.html │ │ └── 500.html │ ├── admin │ │ ├── _user_table.html │ │ ├── pagination.html │ │ ├── claude.html │ │ ├── chat.html │ │ └── admin_user.html │ ├── auth │ │ ├── forgot_password.html │ │ ├── reset_password.html │ │ ├── login.html │ │ ├── register.html │ │ └── verify_otp.html │ ├── main │ │ └── index.html │ ├── user │ │ └── profile.html │ └── base.html ├── __init__.py ├── models.py └── utils.py ├── requirements.txt ├── run.py ├── instance └── config.py ├── README.md └── init_db.sql /flasks/static/css/style.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /flasks/static/js/main.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bear-biscuit/OAIFree_Share/HEAD/requirements.txt -------------------------------------------------------------------------------- /flasks/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bear-biscuit/OAIFree_Share/HEAD/flasks/static/favicon.png -------------------------------------------------------------------------------- /flasks/static/img/gpt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bear-biscuit/OAIFree_Share/HEAD/flasks/static/img/gpt.png -------------------------------------------------------------------------------- /flasks/static/img/claude.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bear-biscuit/OAIFree_Share/HEAD/flasks/static/img/claude.png -------------------------------------------------------------------------------- /flasks/__pycache__/utils.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bear-biscuit/OAIFree_Share/HEAD/flasks/__pycache__/utils.cpython-311.pyc -------------------------------------------------------------------------------- /flasks/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bear-biscuit/OAIFree_Share/HEAD/flasks/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /flasks/__pycache__/models.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bear-biscuit/OAIFree_Share/HEAD/flasks/__pycache__/models.cpython-311.pyc -------------------------------------------------------------------------------- /flasks/blueprints/__pycache__/auth.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bear-biscuit/OAIFree_Share/HEAD/flasks/blueprints/__pycache__/auth.cpython-311.pyc -------------------------------------------------------------------------------- /flasks/blueprints/__pycache__/chat.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bear-biscuit/OAIFree_Share/HEAD/flasks/blueprints/__pycache__/chat.cpython-311.pyc -------------------------------------------------------------------------------- /flasks/blueprints/__pycache__/main.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bear-biscuit/OAIFree_Share/HEAD/flasks/blueprints/__pycache__/main.cpython-311.pyc -------------------------------------------------------------------------------- /flasks/blueprints/__pycache__/user.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bear-biscuit/OAIFree_Share/HEAD/flasks/blueprints/__pycache__/user.cpython-311.pyc -------------------------------------------------------------------------------- /flasks/blueprints/__pycache__/admin.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bear-biscuit/OAIFree_Share/HEAD/flasks/blueprints/__pycache__/admin.cpython-311.pyc -------------------------------------------------------------------------------- /flasks/blueprints/__pycache__/claude.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bear-biscuit/OAIFree_Share/HEAD/flasks/blueprints/__pycache__/claude.cpython-311.pyc -------------------------------------------------------------------------------- /flasks/blueprints/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bear-biscuit/OAIFree_Share/HEAD/flasks/blueprints/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | 4 | # 将 `flasks` 目录的上一级目录加入 Python 路径 5 | # sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '.'))) 6 | from flasks import create_app 7 | 8 | app = create_app() 9 | 10 | if __name__ == '__main__': 11 | app.run(host='0.0.0.0',port=8080, debug=True) 12 | -------------------------------------------------------------------------------- /flasks/templates/error/404.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}页面未找到{% endblock %} 4 | 5 | {% block content %} 6 |
7 |

404 - 页面未找到

8 |

抱歉,您请求的页面不存在。

9 | 返回首页 10 |
11 | {% endblock %} -------------------------------------------------------------------------------- /flasks/templates/error/500.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}服务器错误{% endblock %} 4 | 5 | {% block content %} 6 |
7 |

500 - 服务器错误

8 |

抱歉,服务器出现了问题。请稍后重试。

9 | 返回首页 10 |
11 | {% endblock %} -------------------------------------------------------------------------------- /flasks/blueprints/__init__.py: -------------------------------------------------------------------------------- 1 | from .auth import bp as auth_bp 2 | from .admin import bp as admin_bp 3 | from .main import bp as main_bp 4 | from .user import bp as user_bp 5 | from .chat import bp as chat_bp 6 | from .claude import bp as claude_bp 7 | 8 | # 导出所有蓝图,这样可以在工厂函数中直接使用 from .blueprints import auth, admin, main 9 | auth = auth_bp 10 | admin = admin_bp 11 | main = main_bp 12 | user = user_bp 13 | chat = chat_bp 14 | claude = claude_bp 15 | 16 | __all__ = ['auth', 'admin', 'main', 'chat', 'claude'] -------------------------------------------------------------------------------- /instance/config.py: -------------------------------------------------------------------------------- 1 | # config.py 2 | 3 | # 数据库配置 4 | MYSQL_HOST = '127.0.0.1' 5 | MYSQL_USER = 'root' 6 | MYSQL_PASSWORD = 'password' 7 | MYSQL_DB = 'oaifree' 8 | 9 | # Flask配置 10 | SECRET_KEY = 'awhkwgbfflauws' # 请更改为随机字符串 11 | DEBUG = False # 生产环境设置为False 12 | 13 | # Cookie 和 Session 配置 14 | JWT_EXPIRATION_DAYS = 30 # 记住我的过期时间(天) 15 | COOKIE_SECURE = False # 仅通过HTTPS发送cookie 16 | COOKIE_HTTPONLY = True # 防止XSS攻击的httponly标志 17 | SESSION_COOKIE_SECURE = False # 与COOKIE_SECURE保持一致 18 | SESSION_COOKIE_HTTPONLY = True # 与COOKIE_HTTPONLY保持一致 19 | SESSION_COOKIE_SAMESITE = 'Lax' # 防止CSRF攻击的SameSite策略 20 | 21 | # CSRF 配置 22 | WTF_CSRF_ENABLED = True # 启用CSRF保护 23 | WTF_CSRF_TIME_LIMIT = 3600 # CSRF Token过期时间(秒) 24 | WTF_CSRF_SSL_STRICT = False # 是否严格要求HTTPS(与COOKIE_SECURE保持一致) 25 | WTF_CSRF_CHECK_DEFAULT = True # 默认检查所有POST/PUT/PATCH/DELETE请求 26 | 27 | # 数据库URI 28 | SQLALCHEMY_DATABASE_URI = f'mysql+pymysql://{MYSQL_USER}:{MYSQL_PASSWORD}@{MYSQL_HOST}/{MYSQL_DB}?ssl_disabled=true' 29 | SQLALCHEMY_TRACK_MODIFICATIONS = False 30 | 31 | # 镜像地址 32 | DOMAIN_CHATGPT = 'new.oaifree.com' 33 | DOMAIN_CLAUDE = 'demo.fuclaude.com' 34 | 35 | 36 | 37 | REGISTER = False # 默认不开启注册 38 | 39 | REG_CODE = True # 注册是否需要邀请码 默认需要 40 | 41 | # 邮箱发信相关 开启注册时必须配置 42 | EMAIL_FORNAME = 'AI共享' # 发件者名称 43 | EMAIL_TONAME = '用户' # 收件者名称 44 | EMAIL_API = 'https://email-worker.domain.top/api/send_mail' # cloudflare邮箱后端worker地址 45 | EMAIL_JWT = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZGRyZXNzIjoiYWRtaW5AYm9jY2hpMmIu32sawWwiYWRkcmVzc19pZCI6IjEifQ.wz8aTSwoibHG8shrbMa36pXU-mg-eVt0pRKvvcHl3RU' # 发送邮件的邮箱JWT 46 | 47 | 48 | # 前端相关 49 | DOMAIN_NAME = 'AI' # 网站名 50 | DOMAIN_EMAIL = 'your@mail.com' # “联系我”邮箱 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 项目介绍 2 | 基于始皇的OAIFree服务,搭建一个共享站,方便给自己的小伙伴们使用 3 | 4 | ## 配置项 5 | 6 | 请修改instance\config.py文件中的相关配置 7 | 8 | 关于邮箱部分,使用的是[cloudflare_temp_email](https://github.com/dreamhunter2333/cloudflare_temp_email)项目,请根据作者的部署教程进行部署(非必需) 9 | 10 | ## 部署 11 | 12 | 1、克隆项目 13 | 14 | 2、安装```MySQL``` 15 | 16 | 3、运行```init_db.sql```创建数据库和表 17 | 18 | 4、修改instance\config.py文件中的配置信息 19 | 20 | 5、安装依赖```pip install -r requirements.txt``` 21 | 22 | 6、在项目目录运行```python run.py``` 23 | 24 | 7、如果看到以下字样,就代表运行成功 25 | ``` 26 | 在主进程中初始化自动刷新, 当前时间: 2024-11-07 13:05:04.351826 27 | 设置初始定时器, 延迟秒数: 250715.195757 28 | * Serving Flask app 'run.py' 29 | * Debug mode: off 30 | WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. 31 | * Running on all addresses (0.0.0.0) 32 | * Running on http://127.0.0.1:5000 33 | * Running on http://172.18.0.3:5000 34 | Press CTRL+C to quit 35 | ``` 36 | 37 | 8、使用ip:端口访问 38 | 默认管理员账号 39 | ``` 40 | admin 123123 41 | ``` 42 | ## 页面预览 43 | 登录 44 | ![login](https://github.com/user-attachments/assets/adefeaf2-46cc-445f-a106-0abd7aa176da) 45 | 注册 46 | ![注册](https://github.com/user-attachments/assets/0e1ce0b1-a9be-410a-a5b9-05411b5a2be8) 47 | 2FA 48 | ![2FA](https://github.com/user-attachments/assets/60fc7c26-f9b9-4be2-a5fb-f536f33ab7ea) 49 | 忘记密码 50 | ![忘记密码](https://github.com/user-attachments/assets/041733f5-73af-433f-ab60-dbfed148a76a) 51 | ![重置密码](https://github.com/user-attachments/assets/e29bce0d-e258-420a-8446-ec5fadcfd6a4) 52 | 共享页 53 | ![首页](https://github.com/user-attachments/assets/38928d62-7162-41fa-a9e6-86efffa1ef29) 54 | ![个人页](https://github.com/user-attachments/assets/2854ad46-a4d1-47b2-ac9b-1d3afab62fa0) 55 | 管理员仪表盘 56 | ![管理员仪表盘](https://github.com/user-attachments/assets/8f54e760-5c99-478d-b88f-d46be917ef24) 57 | 用户管理 58 | ![用户设置](https://github.com/user-attachments/assets/cbd89e16-b398-449b-a3bf-6753867e268f) 59 | GPT账号管理 60 | ![gpt](https://github.com/user-attachments/assets/482f767e-89c2-414a-810f-3dde0ec76d3e) 61 | Claude账号管理 62 | ![claude](https://github.com/user-attachments/assets/bba0fdd2-2179-4c8e-98c0-b7863870aad9) 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /flasks/blueprints/user.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint,current_app, render_template, session, request, jsonify 2 | from ..utils import login_required 3 | from ..models import User,db 4 | from werkzeug.security import generate_password_hash, check_password_hash 5 | bp = Blueprint('user', __name__) 6 | 7 | @bp.route('/profile') 8 | @login_required 9 | def index(): 10 | user = User.query.filter_by(username=session['username']).first() 11 | print(user.two_fa) 12 | return render_template('user/profile.html', user=user,email=current_app.config.get('DOMAIN_EMAIL'),title=current_app.config.get('DOMAIN_NAME')) 13 | 14 | # 用户更新密码路由 15 | @bp.route('/api/user/', methods=['PUT']) 16 | @login_required 17 | def uppassword(user_id): 18 | user = User.query.get_or_404(user_id) 19 | data = request.get_json() 20 | if(session.get('user_id')): 21 | try: 22 | if user and check_password_hash(user.password, data['current_password']): 23 | user.password = generate_password_hash(data['password']) 24 | db.session.commit() 25 | return jsonify({'message': '密码更新成功'}), 200 26 | else: 27 | return jsonify({'message': '原始密码错误'}), 400 28 | 29 | except Exception as e: 30 | db.session.rollback() 31 | return jsonify({'error': str(e)}), 400 32 | else: 33 | return jsonify({'error': f"{user_id}匹配失败"}), 400 34 | 35 | 36 | 37 | 38 | # 两步验证 39 | @bp.route('/api/two/', methods=['PUT']) 40 | @login_required 41 | def two_fa(user_id): 42 | user = User.query.get_or_404(user_id) 43 | if(session.get('user_id')): 44 | print(user.two_fa) 45 | try: 46 | if user and user.two_fa: 47 | user.two_fa = False 48 | db.session.commit() 49 | return jsonify({'message': '两步验证已关闭'}), 200 50 | else: 51 | user.two_fa = True 52 | db.session.commit() 53 | return jsonify({'message': '两步验证已开启'}), 200 54 | 55 | except Exception as e: 56 | db.session.rollback() 57 | return jsonify({'error': str(e)}), 400 58 | else: 59 | return jsonify({'error': f"{user_id}匹配失败"}), 400 -------------------------------------------------------------------------------- /flasks/templates/admin/_user_table.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {% for user in users %} 15 | 16 | 19 | 20 | 21 | 24 | 34 | 42 | 43 | {% endfor %} 44 | 45 |
ID用户名邮箱角色最后登录时间操作
17 | {{ user.id }} 18 | {{ user.username }}{{ user.email }} 22 | {{ user.role }} 23 | 25 | {% if user.last_login %} 26 | 27 | 28 | {{ user.last_login }} 29 | 30 | {% else %} 31 | 未登录 32 | {% endif %} 33 | 35 | 38 | 41 |
46 |
47 | 48 | 49 |
50 | 51 | 52 |
53 | {% include "admin/pagination.html" %} 54 |
-------------------------------------------------------------------------------- /flasks/templates/auth/forgot_password.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}忘记密码{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 |
9 |
10 |

11 | 重置密码 12 |

13 | 14 |
15 | 16 |
17 | 18 |
19 | 20 | 21 | 22 | 24 |
25 |
26 | 我们将向您的邮箱发送密码重置链接 27 |
28 |
29 | 30 |
31 | 34 |
35 |
36 | 37 |
38 |

39 | 40 | 返回登录 41 | 42 |

43 |
44 |
45 |
46 |
47 |
48 | 49 | 65 | {% endblock %} -------------------------------------------------------------------------------- /flasks/__init__.py: -------------------------------------------------------------------------------- 1 | import click 2 | from flask import Flask, render_template 3 | from flask_sqlalchemy import SQLAlchemy 4 | 5 | import os 6 | 7 | from flask_wtf import CSRFProtect 8 | 9 | # 初始化数据库对象 10 | db = SQLAlchemy() 11 | 12 | def create_app(test_config=None): 13 | """应用工厂函数""" 14 | app = Flask(__name__, instance_relative_config=True) 15 | 16 | # 默认配置 17 | app.config.from_mapping( 18 | SECRET_KEY='dev', 19 | SQLALCHEMY_DATABASE_URI='sqlite:///' + os.path.join(app.instance_path, 'flaskr.sqlite'), 20 | SQLALCHEMY_TRACK_MODIFICATIONS=False 21 | ) 22 | 23 | if test_config is None: 24 | # 加载实例配置(如果存在) 25 | app.config.from_pyfile('config.py', silent=True) 26 | else: 27 | # 加载测试配置 28 | app.config.update(test_config) 29 | 30 | # 确保实例文件夹存在 31 | try: 32 | os.makedirs(app.instance_path) 33 | except OSError: 34 | pass 35 | 36 | # 初始化扩展 37 | db.init_app(app) 38 | csrf = CSRFProtect(app) 39 | 40 | # 注册蓝图 41 | from .blueprints import auth, admin, main, user, chat, claude 42 | app.register_blueprint(auth) 43 | app.register_blueprint(admin) 44 | app.register_blueprint(main) 45 | app.register_blueprint(user) 46 | app.register_blueprint(chat) 47 | app.register_blueprint(claude) 48 | 49 | # 注册错误处理器 50 | register_error_handlers(app) 51 | 52 | # 注册命令 53 | register_commands(app) 54 | 55 | with app.app_context(): 56 | from .utils import init_auto_refresh 57 | init_auto_refresh() 58 | 59 | return app 60 | 61 | def register_error_handlers(app): 62 | """注册错误处理器""" 63 | @app.errorhandler(404) 64 | def page_not_found(e): 65 | return render_template('error/404.html'), 404 66 | 67 | @app.errorhandler(500) 68 | def internal_server_error(e): 69 | return render_template('error/500.html'), 500 70 | 71 | def register_commands(app): 72 | """注册Flask命令""" 73 | @app.cli.command('init-db') 74 | def init_db_command(): 75 | """初始化数据库""" 76 | from .models import User 77 | from werkzeug.security import generate_password_hash 78 | 79 | db.create_all() 80 | 81 | # 创建默认管理员账户(如果不存在) 82 | admin_user = User.query.filter_by(username='admin').first() 83 | if not admin_user: 84 | admin = User( 85 | username='admin', 86 | password=generate_password_hash('123123'), 87 | email='admin@example.com', 88 | role='admin' 89 | ) 90 | db.session.add(admin) 91 | db.session.commit() 92 | click.echo('Initialized the database and created admin user.') -------------------------------------------------------------------------------- /flasks/templates/auth/reset_password.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}重置密码{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 |
9 |
10 |

11 | 设置新密码 12 |

13 | 14 |
15 | 16 | 17 |
18 | 19 |
20 | 21 | 22 | 23 | 25 |
26 |
27 | 密码长度至少6位 28 |
29 |
30 | 31 |
32 | 33 |
34 | 35 | 36 | 37 | 39 |
40 |
41 | 42 |
43 | 46 |
47 |
48 |
49 |
50 |
51 |
52 | 53 | 80 | {% endblock %} -------------------------------------------------------------------------------- /flasks/blueprints/claude.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, current_app, render_template, request, jsonify 2 | from sqlalchemy import func 3 | from ..models import AutoRefresh, ClaudeToken, User, db 4 | from ..utils import admin_required, login_required,is_valid_email, refresh_access_tokens, schedule_next_refresh 5 | import psutil 6 | from werkzeug.security import generate_password_hash 7 | from datetime import datetime, timedelta 8 | import platform 9 | 10 | 11 | bp = Blueprint('claude', __name__) 12 | 13 | 14 | 15 | 16 | 17 | @bp.route('/claude') 18 | @admin_required 19 | def claude(): 20 | return render_template('admin/claude.html',email=current_app.config.get('DOMAIN_EMAIL'),title=current_app.config.get('DOMAIN_NAME')) 21 | 22 | 23 | # 加载Refresh Token 24 | @bp.route('/get_Claude') 25 | @admin_required 26 | def get_Claude(): 27 | try: 28 | tokens = ClaudeToken.query.all() 29 | token_list = [token.to_dict() for token in tokens] 30 | return jsonify(token_list), 200 31 | except FileNotFoundError: 32 | return jsonify([]), 200 # 如果文件不存在,返回空列表 33 | 34 | 35 | # 添加新账号 36 | @bp.route('/api/Claude', methods=['POST']) 37 | @admin_required 38 | def create_Claude(): 39 | data = request.get_json() 40 | 41 | if ClaudeToken.query.filter_by(email=data['email']).first(): 42 | return jsonify({'success': False, 'message': '该账号已存在'}), 400 43 | 44 | try: 45 | # 添加新账号 46 | new_chat = ClaudeToken( 47 | email = data['email'], 48 | skToken = data['SkToken'], 49 | status = True, 50 | type ="/static/img/claude.png", 51 | PLUS = data['PLUS'] 52 | ) 53 | db.session.add(new_chat) 54 | db.session.commit() 55 | 56 | return jsonify({'success': True, 'message': '添加成功'}) 57 | 58 | except Exception as e: 59 | db.session.rollback() 60 | print(e) 61 | return jsonify({'success': False, 'message': '添加失败'}) 62 | 63 | 64 | # 更新账号信息 65 | @bp.route('/api/Claude/', methods=['PUT']) 66 | @admin_required 67 | def update_Claude(email): 68 | data = request.get_json() 69 | tokens = ClaudeToken.query.filter(ClaudeToken.email == email) 70 | token = ClaudeToken.query.filter(ClaudeToken.email == email).first() 71 | 72 | if not tokens: 73 | return jsonify({'success': False, 'message': '账号不存在'}), 404 74 | 75 | # 如果提供了邮箱,则更新邮箱 76 | if data.get('email'): 77 | token.email = data['email'] 78 | 79 | # 如果提供了ReToken,则更新ReToken 80 | if data.get('SkToken'): 81 | token.SkToken = data['SkToken'] 82 | token.status = True 83 | 84 | if data.get('PLUS'): 85 | token.PLUS = data['PLUS'] 86 | db.session.commit() 87 | 88 | return jsonify({'success': True, 'message': '账号更新成功'}) 89 | 90 | # 删除账号 91 | @bp.route('/api/Claude/', methods=['DELETE']) 92 | @admin_required 93 | def delete_Claude(email): 94 | 95 | email = ClaudeToken.query.filter(ClaudeToken.email == email).first() 96 | 97 | try: 98 | db.session.delete(email) 99 | db.session.commit() 100 | return jsonify({'success': True, 'message': '账号删除成功'}) 101 | except Exception as e: 102 | db.session.rollback() 103 | return jsonify({'success': False, 'message': '删除失败'}), 404 -------------------------------------------------------------------------------- /flasks/templates/auth/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}登录{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 |
9 |
10 |

11 | 用户登录 12 |

13 | 14 |
15 | 16 |
17 | 18 |
19 | 20 | 21 | 22 | 24 |
25 |
26 | 27 |
28 | 29 |
30 | 31 | 32 | 33 | 35 |
36 | 41 |
42 | 43 |
44 | 45 | 46 |
47 | 48 |
49 | 52 |
53 |
54 | 55 |
56 |

还没有账号? 57 | 立即注册 58 |

59 |
60 |
61 |
62 |
63 |
64 | 65 | 81 | {% endblock %} -------------------------------------------------------------------------------- /flasks/templates/admin/pagination.html: -------------------------------------------------------------------------------- 1 | 71 | -------------------------------------------------------------------------------- /init_db.sql: -------------------------------------------------------------------------------- 1 | -- 创建数据库 2 | CREATE DATABASE IF NOT EXISTS oaifree; 3 | 4 | -- 选择使用该数据库 5 | USE oaifree; 6 | 7 | -- 创建用户表 8 | 9 | SET NAMES utf8mb4; 10 | 11 | DROP TABLE IF EXISTS `user`; 12 | CREATE TABLE `user` ( 13 | `id` int NOT NULL AUTO_INCREMENT, 14 | `username` varchar(80) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, 15 | `password` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, 16 | `email` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, 17 | `role` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'user', 18 | `last_login` datetime NULL DEFAULT NULL, 19 | `last_login_ip` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, 20 | `registered_at` datetime NULL DEFAULT CURRENT_TIMESTAMP, 21 | `otp_code` varchar(6) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, 22 | `otp_expiration` datetime NULL DEFAULT NULL, 23 | `two_fa` tinyint(1) NULL DEFAULT 0, 24 | PRIMARY KEY (`id`) USING BTREE, 25 | UNIQUE INDEX `username`(`username` ASC) USING BTREE, 26 | UNIQUE INDEX `email`(`email` ASC) USING BTREE 27 | ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC; 28 | 29 | INSERT INTO `user` VALUES (1, 'admin', 'scrypt:32768:8:1$DK8XDdwapTIPJn0b$27eb523d1b6277781caf005d2c2bdd5a213dcae8d400bb44cb8896cad129a669ea3fa9188ffa51c021210ca215b5e1d0c0a45072d03bec5ac82bfa64cde09e6b', 'bocchi2b@outlook.com', 'admin', '2024-11-01 12:53:03', '192.168.3.130', '2024-10-20 14:18:50', NULL, NULL, 0); 30 | 31 | 32 | -- 创建chat自动任务配置表 33 | SET NAMES utf8mb4; 34 | 35 | 36 | DROP TABLE IF EXISTS `auto_refresh`; 37 | CREATE TABLE `auto_refresh` ( 38 | `id` int NOT NULL AUTO_INCREMENT, 39 | `auto_refresh_enabled` tinyint(1) NULL DEFAULT NULL, 40 | `refresh_interval_days` int NULL DEFAULT NULL, 41 | `next_refresh_time` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, 42 | PRIMARY KEY (`id`) USING BTREE 43 | ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; 44 | 45 | 46 | INSERT INTO `auto_refresh` VALUES (1, 1, 8, '2024-11-10T10:43:40.036153'); 47 | 48 | 49 | -- 创建chat表 50 | SET NAMES utf8mb4; 51 | 52 | 53 | DROP TABLE IF EXISTS `chat_token`; 54 | CREATE TABLE `chat_token` ( 55 | `id` int NOT NULL AUTO_INCREMENT, 56 | `email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, 57 | `refresh_token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, 58 | `access_token` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, 59 | `status` tinyint(1) NULL DEFAULT NULL, 60 | `type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, 61 | `PLUS` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, 62 | PRIMARY KEY (`id`) USING BTREE 63 | ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; 64 | 65 | 66 | 67 | -- 创建claude表 68 | SET NAMES utf8mb4; 69 | 70 | 71 | 72 | DROP TABLE IF EXISTS `claude_token`; 73 | CREATE TABLE `claude_token` ( 74 | `id` int NOT NULL AUTO_INCREMENT, 75 | `email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, 76 | `skToken` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, 77 | `status` tinyint(1) NULL DEFAULT NULL, 78 | `type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, 79 | `PLUS` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, 80 | PRIMARY KEY (`id`) USING BTREE 81 | ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; 82 | 83 | 84 | 85 | -- 创建邀请码表 86 | SET NAMES utf8mb4; 87 | 88 | 89 | DROP TABLE IF EXISTS `invitation_codes`; 90 | CREATE TABLE `invitation_codes` ( 91 | `code` char(6) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, 92 | PRIMARY KEY (`code`) USING BTREE 93 | ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; 94 | 95 | 96 | -- 创建统计表 97 | SET NAMES utf8mb4; 98 | 99 | DROP TABLE IF EXISTS `record`; 100 | CREATE TABLE `record` ( 101 | `id` int NOT NULL AUTO_INCREMENT, 102 | `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, 103 | `email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, 104 | `access_token` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, 105 | `shared_token` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, 106 | `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, 107 | PRIMARY KEY (`id`) USING BTREE 108 | ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; 109 | 110 | -------------------------------------------------------------------------------- /flasks/models.py: -------------------------------------------------------------------------------- 1 | from . import db 2 | from datetime import datetime 3 | 4 | class User(db.Model): 5 | """用户模型""" 6 | id = db.Column(db.Integer, primary_key=True) 7 | username = db.Column(db.String(80), unique=True, nullable=False) 8 | password = db.Column(db.String(200), nullable=False) 9 | email = db.Column(db.String(120), unique=True, nullable=False) 10 | role = db.Column(db.String(20), nullable=False, default='user') 11 | last_login = db.Column(db.DateTime) 12 | last_login_ip = db.Column(db.String(100)) 13 | registered_at = db.Column(db.DateTime, default=datetime.utcnow) 14 | otp_code = db.Column(db.String(6), nullable=True) 15 | otp_expiration = db.Column(db.DateTime, nullable=True) 16 | two_fa = db.Column(db.Boolean, default=False, nullable=False) 17 | 18 | def __repr__(self): 19 | return f'' 20 | 21 | class AutoRefresh(db.Model): 22 | """自动刷新模型""" 23 | __tablename__ = 'auto_refresh' 24 | 25 | id = db.Column(db.Integer, primary_key=True) 26 | auto_refresh_enabled = db.Column(db.Boolean, default=False, nullable=False) 27 | refresh_interval_days = db.Column(db.Integer, nullable=False) 28 | next_refresh_time = db.Column(db.String(100), nullable=False) 29 | 30 | def __repr__(self): 31 | return f'' 32 | 33 | def to_dict(self): 34 | return { 35 | 'id': self.id, 36 | 'auto_refresh_enabled': self.auto_refresh_enabled, 37 | 'refresh_interval_days': self.refresh_interval_days, 38 | 'next_refresh_time': self.next_refresh_time 39 | } 40 | 41 | class ChatToken(db.Model): 42 | """ChatGPT 令牌模型""" 43 | __tablename__ = 'chat_token' 44 | 45 | id = db.Column(db.Integer, primary_key=True) 46 | email = db.Column(db.String(255), unique=True, nullable=False) 47 | refresh_token = db.Column(db.String(255), nullable=True) 48 | access_token = db.Column(db.Text, nullable=True) 49 | status = db.Column(db.Boolean, default=True, nullable=False) 50 | type = db.Column(db.String(255), nullable=False) 51 | PLUS = db.Column(db.String(10), default='False', nullable=False) 52 | 53 | def __repr__(self): 54 | return f'' 55 | 56 | def to_dict(self): 57 | return { 58 | 'id': self.id, 59 | 'email': self.email, 60 | 'refresh_token': self.refresh_token, 61 | 'access_token': self.access_token, 62 | 'status': self.status, 63 | 'type': self.type, 64 | 'PLUS': self.PLUS 65 | } 66 | 67 | class ClaudeToken(db.Model): 68 | """Claude 令牌模型""" 69 | __tablename__ = 'claude_token' 70 | 71 | id = db.Column(db.Integer, primary_key=True) 72 | email = db.Column(db.String(255), unique=True, nullable=False) 73 | skToken = db.Column(db.String(255), nullable=False) 74 | status = db.Column(db.Boolean, default=True, nullable=False) 75 | type = db.Column(db.String(255), nullable=True) 76 | PLUS = db.Column(db.String(10), default='False', nullable=False) 77 | 78 | def __repr__(self): 79 | return f'' 80 | 81 | def to_dict(self): 82 | return { 83 | 'id': self.id, 84 | 'email': self.email, 85 | 'skToken': self.skToken, 86 | 'status': self.status, 87 | 'type': self.type, 88 | 'PLUS': self.PLUS 89 | } 90 | 91 | class Record(db.Model): 92 | """用量记录模型""" 93 | __tablename__ = 'record' 94 | 95 | id = db.Column(db.Integer, primary_key=True) 96 | username = db.Column(db.String(255), nullable=False) 97 | email = db.Column(db.String(255), nullable=False) 98 | access_token = db.Column(db.Text, nullable=False) 99 | shared_token = db.Column(db.Text, nullable=False) 100 | created_at = db.Column(db.DateTime, default=datetime.utcnow) 101 | 102 | def __repr__(self): 103 | return f"" 104 | 105 | def to_dict(self): 106 | return { 107 | 'username': self.username, 108 | 'email': self.email, 109 | 'access_token': self.access_token, 110 | 'shared_token': self.shared_token, 111 | 'created_at': self.created_at 112 | } 113 | def to_dict_user(self): 114 | return { 115 | 'username': self.username 116 | } 117 | def to_dict_email(self): 118 | return { 119 | 'email': self.email 120 | } 121 | 122 | class InvitationCodes(db.Model): 123 | """注册邀请码""" 124 | __tablename__ = 'invitation_codes' 125 | code = db.Column(db.String(6), primary_key=True) 126 | 127 | def __repr__(self): 128 | return f'' 129 | 130 | def to_dict(self): 131 | return { 132 | 'code': self.code 133 | } -------------------------------------------------------------------------------- /flasks/blueprints/chat.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, current_app, render_template, request, jsonify 2 | from sqlalchemy import func 3 | from ..models import AutoRefresh, ChatToken, Record, User, db 4 | from ..utils import admin_required, login_required,is_valid_email, refresh_access_tokens, schedule_next_refresh, usage_record 5 | import psutil 6 | from werkzeug.security import generate_password_hash 7 | from datetime import datetime, timedelta 8 | import platform 9 | 10 | 11 | bp = Blueprint('chat', __name__) 12 | 13 | # chatgpt页 14 | @bp.route('/chatgpt', methods=['GET', 'POST']) 15 | @admin_required 16 | def chatgpt(): 17 | if request.method == 'GET': 18 | 19 | return render_template('admin/chat.html',email=current_app.config.get('DOMAIN_EMAIL'),title=current_app.config.get('DOMAIN_NAME')) 20 | 21 | if request.method == 'POST': 22 | # 获取更新后的 retoken 数据 23 | new_retokens = request.json.get('retokens') 24 | 25 | # 如果数据格式有效,保存到文件 26 | if new_retokens: 27 | ChatToken.refresh_token = new_retokens 28 | db.session.commit() 29 | return jsonify({"status": "success", "message": "chatToken.json 已更新!"}), 200 30 | else: 31 | return jsonify({"status": "error", "message": "无效的数据格式!"}), 400 32 | 33 | 34 | # 设定定时任务 35 | @bp.route('/set_auto_refresh', methods=['POST']) 36 | @admin_required 37 | def set_auto_refresh(): 38 | data = request.json 39 | config = AutoRefresh.query.filter_by(id=1).first() 40 | 41 | # 取消现有的定时任务 42 | config.auto_refresh_enabled = data['enabled'] 43 | config.refresh_interval_days = data['interval'] 44 | db.session.commit() 45 | 46 | if config.auto_refresh_enabled: 47 | schedule_next_refresh() 48 | 49 | return jsonify({"status": "success", "message": "自动刷新设置已更新"}) 50 | 51 | # 加载定时任务配置信息 52 | @bp.route('/get_auto_refresh_config', methods=['GET']) 53 | def get_auto_refresh_config(): 54 | config = AutoRefresh.query.filter_by(id=1).first() 55 | if config is None: 56 | return jsonify({'message': '未找到配置'}), 404 57 | return jsonify(config.to_dict()), 200 58 | 59 | 60 | 61 | 62 | # 手动刷新access token 63 | @bp.route('/refresh_tokens', methods=['POST']) 64 | @admin_required 65 | def refresh_tokens(): 66 | try: 67 | # 调用刷新 access_token 的函数 68 | refresh_access_tokens() 69 | 70 | return jsonify({ 71 | "status": "success", 72 | }), 200 73 | except Exception as e: 74 | return jsonify({ 75 | "status": "error", 76 | "message": str(e) 77 | }), 500 78 | 79 | 80 | # 加载账号 81 | @bp.route('/get_tokens') 82 | @admin_required 83 | def get_tokens(): 84 | try: 85 | tokens = ChatToken.query.all() 86 | token_list = [token.to_dict() for token in tokens] 87 | return jsonify(token_list), 200 88 | except FileNotFoundError: 89 | return jsonify([]), 200 # 如果文件不存在,返回空列表 90 | 91 | 92 | # 添加新账号 93 | @bp.route('/api/tokens', methods=['POST']) 94 | @admin_required 95 | def create_tokens(): 96 | data = request.get_json() 97 | 98 | if ChatToken.query.filter_by(email=data['email']).first(): 99 | return jsonify({'success': False, 'message': '该账号已存在'}), 400 100 | 101 | try: 102 | # 添加新账号 103 | new_chat = ChatToken( 104 | email = data['email'], 105 | refresh_token = data['ReToken'], 106 | access_token = data['AcToken'], 107 | status = True, 108 | type ="/static/img/gpt.png", 109 | PLUS = data['PLUS'] 110 | ) 111 | db.session.add(new_chat) 112 | db.session.commit() 113 | 114 | return jsonify({'success': True, 'message': '添加成功'}) 115 | 116 | except Exception as e: 117 | db.session.rollback() 118 | 119 | return jsonify({'success': False, 'message': '添加失败'}) 120 | 121 | 122 | # 更新账号信息 123 | @bp.route('/api/tokens/', methods=['PUT']) 124 | @admin_required 125 | def update_token(email): 126 | data = request.get_json() 127 | tokens = ChatToken.query.filter(ChatToken.email == email) 128 | token = ChatToken.query.filter(ChatToken.email == email).first() 129 | 130 | if not tokens: 131 | return jsonify({'success': False, 'message': '账号不存在'}), 404 132 | 133 | # 如果提供了邮箱,则更新邮箱 134 | if data.get('email'): 135 | token.email = data['email'] 136 | 137 | # 如果提供了ReToken,则更新ReToken 138 | if data.get('ReToken'): 139 | token.refresh_token = data['ReToken'] 140 | else: 141 | token.refresh_token = '' 142 | 143 | # 如果提供了AcToken,则更新AcToken 144 | if data.get('AcToken'): 145 | token.access_token = data['AcToken'] 146 | token.status = True 147 | else: 148 | token.access_token = '' 149 | if data.get('PLUS'): 150 | token.PLUS = data['PLUS'] 151 | db.session.commit() 152 | 153 | return jsonify({'success': True, 'message': '账号更新成功'}) 154 | 155 | # 删除账号 156 | @bp.route('/api/tokens/', methods=['DELETE']) 157 | @admin_required 158 | def delete_token(email): 159 | 160 | email = ChatToken.query.filter(ChatToken.email == email).first() 161 | 162 | try: 163 | db.session.delete(email) 164 | db.session.commit() 165 | return jsonify({'success': True, 'message': '账号删除成功'}) 166 | except Exception as e: 167 | db.session.rollback() 168 | return jsonify({'success': False, 'message': '删除失败'}), 404 169 | 170 | 171 | -------------------------------------------------------------------------------- /flasks/blueprints/main.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint,current_app, render_template, session, request, jsonify 2 | from ..utils import check_final_url, get_claude_login_url, getoauth, login_required, register_token 3 | from ..models import ChatToken, ClaudeToken, Record, User,db 4 | from werkzeug.security import generate_password_hash, check_password_hash 5 | bp = Blueprint('main', __name__) 6 | 7 | @bp.route('/') 8 | @login_required 9 | def index(): 10 | # 读取 chatToken.json 中的 access_tokens 11 | gpt_tokens_list = ChatToken.query.all() 12 | gpt_tokens = [token.to_dict() for token in gpt_tokens_list] 13 | 14 | # 读取 claudeToken.json 中的 token 15 | claude_tokens_list = ClaudeToken.query.all() 16 | claude_tokens = [token.to_dict() for token in claude_tokens_list] 17 | 18 | # 创建两个列表分别存储PLUS和普通账号 19 | gpt_plus_tokens = [] 20 | gpt_normal_tokens = [] 21 | 22 | claude_plus_tokens = [] 23 | claude_normal_tokens = [] 24 | 25 | # 遍历所有token并分组 26 | for idx, token in enumerate(gpt_tokens): 27 | if token['status']: # 如果token有效 28 | token_info = { 29 | 'index': idx, 30 | 'type': token.get('type', 'unknown'), 31 | 'PLUS': token.get('PLUS', 'false') 32 | } 33 | 34 | # 根据PLUS状态分组 35 | if token_info['PLUS'].lower() == 'true': 36 | gpt_plus_tokens.append(token_info) 37 | else: 38 | gpt_normal_tokens.append(token_info) 39 | 40 | # 遍历所有token并分组 41 | for idx, token in enumerate(claude_tokens): 42 | if token['status']: # 如果token有效 43 | token_info = { 44 | 'index': idx, 45 | 'type': token.get('type', 'unknown'), 46 | 'PLUS': token.get('PLUS', 'false') 47 | } 48 | 49 | # 根据PLUS状态分组 50 | if token_info['PLUS'].lower() == 'true': 51 | 52 | claude_plus_tokens.append(token_info) 53 | else: 54 | claude_normal_tokens.append(token_info) 55 | 56 | 57 | # 按顺序合并两个列表,PLUS账号在前 58 | gpt_valid_tokens = gpt_plus_tokens + gpt_normal_tokens 59 | 60 | claude_valid_tokens = claude_plus_tokens + claude_normal_tokens 61 | 62 | 63 | # 渲染模板,传递有效token的详细信息 64 | return render_template( 65 | 'main/index.html', 66 | gpt_valid_tokens=gpt_valid_tokens, # 传递排序后的token列表 67 | claude_valid_tokens= claude_valid_tokens, 68 | email=current_app.config.get('DOMAIN_EMAIL'), 69 | title=current_app.config.get('DOMAIN_NAME') 70 | ) 71 | 72 | # 用于处理 UNIQUE_NAME 的提交 73 | @bp.route('/submit_name', methods=['POST']) 74 | @login_required 75 | def submit_name(): 76 | data = request.json 77 | unique_name = data.get('unique_name') 78 | index = data.get('index') 79 | Type = data.get('type') 80 | 81 | 82 | if Type == 'chatgpt': 83 | 84 | tokens_list = ChatToken.query.all() 85 | tokens = [token.to_dict() for token in tokens_list] 86 | valid_tokens = tokens 87 | 88 | # 确保 index 是有效的索引 89 | if index and 1 <= index <= len(valid_tokens): 90 | 91 | # 获取对应的 access_token 92 | token_info = valid_tokens[index - 1] 93 | 94 | access_token = token_info['access_token'] 95 | 96 | email = token_info['email'] 97 | 98 | 99 | new_access_token = access_token 100 | 101 | if 'http' in access_token or 'https' in access_token: 102 | old_url = "https://plus.aivvm.com/" 103 | new_url = "https://gpt.bocchi2b.top/" 104 | 105 | # 替换旧的 URL 106 | new_access_token = new_access_token.replace(old_url, new_url) 107 | 108 | url = new_access_token # 你要测试的原始链接 109 | 110 | target_url = 'https://gpt.bocchi2b.top/auth/login_auth0' # 你要匹配的目标地址 111 | logurl = check_final_url(url, target_url) 112 | 113 | if logurl: 114 | return jsonify({ 115 | "status": "success", 116 | "login_url": access_token 117 | }), 200 118 | else: 119 | token = ChatToken.query.filter(ChatToken.access_token == access_token).first() 120 | token.status = False 121 | db.session.commit() 122 | 123 | return jsonify({ 124 | "status": "error", 125 | "message": "账号失效了,换一个吧" 126 | }), 400 127 | 128 | # 注册 token 并获取对应的 token_key 129 | token_key = register_token(access_token, unique_name) 130 | # token_key = None 131 | 132 | if not token_key: 133 | # 更新 chatToken.json 中对应条目的 status 为 false 134 | token = ChatToken.query.filter(ChatToken.access_token == access_token).first() 135 | token.status = False 136 | db.session.commit() 137 | 138 | # 清除用量表中对应的access_token记录 139 | records_to_delete = Record.query.filter_by(access_token=access_token).all() 140 | for record in records_to_delete: 141 | db.session.delete(record) 142 | db.session.commit() 143 | 144 | return jsonify({ 145 | "status": "error", 146 | "message": "账号失效了,换一个吧" 147 | }), 400 148 | 149 | # 检查是否存在相同的shared_token 150 | if not Record.query.filter_by(shared_token=token_key).first(): 151 | new_record = Record( 152 | username=unique_name, 153 | email=email, 154 | access_token=access_token, 155 | shared_token=token_key 156 | ) 157 | db.session.add(new_record) 158 | db.session.commit() 159 | 160 | # 获取 OAuth 登录 URL 161 | logurl = getoauth(token_key) 162 | 163 | 164 | return jsonify({ 165 | "status": "success", 166 | "login_url": logurl 167 | }), 200 168 | else: 169 | return jsonify({ 170 | "status": "error", 171 | "message": "Invalid index." 172 | }), 400 173 | else: 174 | # tokens = load_cltoken() 175 | tokens_list = ClaudeToken.query.all() 176 | tokens = [token.to_dict() for token in tokens_list] 177 | valid_tokens = tokens 178 | 179 | # 确保 index 是有效的索引 180 | if index and 1 <= index <= len(valid_tokens): 181 | 182 | # 获取对应的 token 183 | token_info = valid_tokens[index - 1] 184 | 185 | skToken = token_info['skToken'] 186 | 187 | # 获取登录链接 188 | logurl = get_claude_login_url(skToken, unique_name) 189 | if not logurl: 190 | # 更新 chatToken.json 中对应条目的 status 为 false 191 | token = ClaudeToken.query.filter(ClaudeToken.skToken == skToken).first() 192 | token.status = False 193 | db.session.commit() 194 | 195 | return jsonify({ 196 | "status": "error", 197 | "message": "账号失效了,换一个吧" 198 | }), 400 199 | 200 | return jsonify({ 201 | "status": "success", 202 | "login_url": logurl 203 | }), 200 204 | else: 205 | return jsonify({ 206 | "status": "error", 207 | "message": "Invalid index." 208 | }), 400 -------------------------------------------------------------------------------- /flasks/templates/auth/register.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}注册{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 |
9 |
10 |

11 | 用户注册 12 |

13 | 14 |
15 | 16 |
17 | 18 |
19 | 20 | 21 | 22 | 24 |
25 |
用户名至少需要3个字符
26 |
27 | 28 |
29 | 30 |
31 | 32 | 33 | 34 | 36 | 39 |
40 |
41 | 42 |
43 | 44 |
45 | 46 | 47 | 48 | 50 |
51 |
52 | 53 |
54 | 55 |
56 | 57 | 58 | 59 | 61 |
62 |
63 | 64 |
65 | 66 |
67 | 68 | 69 | 70 | 72 |
73 |
密码至少需要6个字符
74 |
75 | 76 |
77 | 78 |
79 | 80 | 81 | 82 | 84 |
85 |
86 | 87 | 88 |
89 | 90 | 93 |
94 | 95 |
96 | 99 |
100 |
101 | 102 |
103 |

已有账号? 104 | 立即登录 105 |

106 |
107 |
108 |
109 |
110 |
111 | 112 | 193 | {% endblock %} -------------------------------------------------------------------------------- /flasks/templates/main/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}首页{% endblock %} 4 | 5 | {% block content %} 6 | 100 | 141 | 142 | 179 | 180 | 181 |
182 | {% if gpt_valid_tokens %} 183 | {% for token in gpt_valid_tokens %} 184 | 195 | {% endfor %} 196 | {% else %} 197 |

暂无GPT账号

198 | {% endif %} 199 |
200 | 201 |

202 | 203 |
204 | {% if claude_valid_tokens %} 205 | {% for token in claude_valid_tokens %} 206 | 217 | {% endfor %} 218 | {% else %} 219 |

暂无Claude账号

220 | {% endif %} 221 |
222 | 223 | 271 | {% endblock %} -------------------------------------------------------------------------------- /flasks/templates/user/profile.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}个人中心{% endblock %} 4 | 5 | {% block content %} 6 | 14 |
15 | 16 |
17 |
18 |
19 |
20 |
21 |
22 | {{ user.username[0].upper() }} 23 |
24 |

{{ user.username }}

25 |

{{ user.email }}

26 | {{ user.role }} 27 |
28 | 29 |
30 | 31 |
32 |
33 |
34 |
35 |
36 | 账户信息 37 |
38 |
    39 |
  • 40 | 账户ID: 41 | {{ user.id }} 42 |
  • 43 |
  • 44 | 注册时间: 45 | {{user.registered_at}} 46 |
  • 47 |
  • 48 | 最后登录: 49 | {{ user.last_login.strftime('%Y-%m-%d %H:%M:%S') if 50 | user.last_login else '暂无记录' }} 51 |
  • 52 |
53 |
54 |
55 |
56 | 57 |
58 |
59 |
60 |
61 | 安全设置 62 |
63 |
    64 |
  • 65 |
    66 |
    修改密码
    67 | 定期更改密码以保护账户安全 68 |
    69 | 72 |
  • 73 |
  • 74 |
    75 |
    两步验证
    76 | 增加额外的账户安全性 77 |
    78 | {% if user.two_fa %} 79 |
    80 | 82 |
    83 | {% else %} 84 |
    85 | 87 |
    88 | {% endif %} 89 |
  • 90 |
91 |
92 |
93 |
94 |
95 | 96 |
97 |
98 |
99 |
100 | 101 | 102 | 138 | 139 | 140 | 245 | {% endblock %} -------------------------------------------------------------------------------- /flasks/templates/auth/verify_otp.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}验证码验证{% endblock %} 4 | 5 | {% block content %} 6 | 7 | 209 |
210 |
211 |
212 | 213 |
214 |

验证你的邮箱

215 |

216 | 我们已向 发送验证码 217 |

218 |
219 | 220 |
221 | 222 | 223 |
224 | {% for i in range(6) %} 225 | 232 | {% endfor %} 233 |
234 | 235 | 236 |
237 | 05:00 238 | 244 |
245 | 246 | 252 | 253 | 259 |
260 |
261 |
262 |
263 | 264 | 414 | {% endblock %} -------------------------------------------------------------------------------- /flasks/blueprints/auth.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, current_app, render_template, request, redirect, url_for, flash, session, jsonify, make_response 2 | from werkzeug.security import generate_password_hash, check_password_hash 3 | 4 | from ..models import InvitationCodes, User, db 5 | from ..utils import generate_remember_token, generate_reset_token, get_user_ip, send_otp_email, send_reset_email, send_verification_email, generate_verification_code, verify_code, verify_otp_code 6 | from datetime import datetime, timedelta 7 | 8 | bp = Blueprint('auth', __name__) 9 | 10 | @bp.route('/send-verification-code', methods=['POST']) 11 | def send_verification_code(): 12 | """发送验证码的路由""" 13 | data = request.get_json() 14 | email = data.get('email') 15 | 16 | if not email: 17 | return jsonify({'success': False, 'message': '请提供邮箱地址'}), 400 18 | 19 | # 检查是否在冷却时间内 20 | last_send_time = session.get(f'last_send_time_{email}') 21 | if last_send_time: 22 | last_send_time = datetime.fromisoformat(last_send_time) 23 | if datetime.now() - last_send_time < timedelta(minutes=1): 24 | return jsonify({'success': False, 'message': '请求频率过高,请稍后再试'}), 429 25 | 26 | # 生成验证码 27 | code = generate_verification_code() 28 | 29 | 30 | # 发送验证码 31 | if send_verification_email(email, code): 32 | # 存储验证码和发送时间 33 | session[f'verification_code_{email}'] = code 34 | session[f'code_expire_time_{email}'] = (datetime.now() + timedelta(minutes=5)).isoformat() 35 | session[f'last_send_time_{email}'] = datetime.now().isoformat() 36 | return jsonify({'success': True, 'message': '验证码已发送'}), 200 37 | else: 38 | return jsonify({'success': False, 'message': '验证码发送失败'}), 500 39 | 40 | 41 | @bp.route('/register', methods=['GET', 'POST']) 42 | def register(): 43 | 44 | if not current_app.config.get('REGISTER'): 45 | flash('暂未开启注册', 'danger') 46 | return redirect(url_for('auth.login')) 47 | 48 | if 'username' in session: 49 | return redirect(url_for('main.index')) 50 | 51 | if request.method == 'POST': 52 | # 获取表单数据 53 | username = request.form.get('username', '').strip() 54 | password = request.form.get('password', '').strip() 55 | email = request.form.get('email', '').strip() 56 | verification_code = request.form.get('verification_code', '').strip() 57 | vecode = request.form.get('vcode', '').strip() 58 | 59 | # 判断是否是AJAX请求 60 | is_ajax = request.headers.get('X-Requested-With') == 'XMLHttpRequest' 61 | 62 | def error_response(message): 63 | """统一错误响应处理""" 64 | flash(message, 'danger') 65 | if is_ajax: 66 | return jsonify({'success': False, 'message': message}), 400 67 | return render_template('auth/register.html') 68 | 69 | 70 | # 基础验证 71 | if not all([username, password, email, verification_code]): 72 | return error_response('请填写所有必需的字段!') 73 | 74 | # 验证码检查 75 | if not verify_code(email, verification_code): 76 | return error_response('验证码无效或已过期') 77 | 78 | # 表单验证 79 | if len(username) < 3: 80 | return error_response('用户名至少需要3个字符!') 81 | 82 | if len(password) < 6: 83 | return error_response('密码至少需要6个字符!') 84 | 85 | # 检查用户名是否已存在 86 | if User.query.filter_by(username=username).first(): 87 | return error_response('用户名已存在!') 88 | 89 | # 检查邮箱是否已存在 90 | if User.query.filter_by(email=email).first(): 91 | return error_response('邮箱已被注册!') 92 | 93 | if current_app.config.get('REG_CODE'): 94 | # 邀请码验证 95 | code_entry = InvitationCodes.query.filter_by(code=vecode).first() 96 | 97 | if not code_entry: 98 | return error_response('邀请码错误!') 99 | 100 | db.session.delete(code_entry) 101 | db.session.commit() 102 | 103 | try: 104 | # 创建新用户 105 | hashed_password = generate_password_hash(password) 106 | new_user = User( 107 | username=username, 108 | password=hashed_password, 109 | email=email, 110 | role='user' # 默认角色为普通用户 111 | ) 112 | db.session.add(new_user) 113 | db.session.commit() 114 | 115 | # 清除验证码 116 | session.pop(f'verification_code_{email}', None) 117 | session.pop(f'code_expire_time_{email}', None) 118 | 119 | # 注册成功响应 120 | flash('注册成功!请登录。', 'success') 121 | if is_ajax: 122 | return jsonify({ 123 | 'success': True, 124 | 'message': '注册成功!请登录。', 125 | 'redirect': url_for('auth.login') 126 | }), 200 127 | return redirect(url_for('auth.login')) 128 | 129 | except Exception as e: 130 | db.session.rollback() 131 | print(e) 132 | return error_response(f'注册失败,请稍后重试。{e}') 133 | 134 | # GET 请求返回注册页面 135 | return render_template('auth/register.html',email=current_app.config.get('DOMAIN_EMAIL'),title=current_app.config.get('DOMAIN_NAME')) 136 | 137 | @bp.route('/login', methods=['GET', 'POST']) 138 | def login(): 139 | if 'username' in session: 140 | return redirect(url_for('main.index')) 141 | 142 | if request.method == 'POST': 143 | username = request.form['username'] 144 | password = request.form['password'] 145 | remember = request.form.get('remember') == 'on' 146 | user_ip = get_user_ip() 147 | user = User.query.filter_by(username=username).first() 148 | if user is None: 149 | # 处理用户未找到的情况 150 | flash(‘用户名或密码错误!’, danger') 151 | return redirect(url_for('auth.login')) 152 | if not user.two_fa or user_ip == user.last_login_ip: 153 | 154 | if user and check_password_hash(user.password, password): 155 | 156 | session['username'] = user.username 157 | session['user_id'] = user.id 158 | session['role'] = user.role 159 | # 处理"记住我" 160 | response = make_response(redirect(url_for('main.index'))) 161 | if remember: 162 | # 生成记住我令牌 163 | remember_token = generate_remember_token(user.id) 164 | # 设置cookie,过期时间30天 165 | response.set_cookie( 166 | 'remember_token', 167 | remember_token, 168 | max_age=60 * 60 *24 * current_app.config.get('JWT_EXPIRATION_DAYS',30), 169 | httponly=True, # 防止XSS攻击 170 | secure = current_app.config.get('COOKIE_SECURE') # 仅通过HTTPS发送 171 | ) 172 | 173 | # 更新最后登录时间 174 | user.last_login = datetime.utcnow() 175 | # 记录本次登录ip 176 | user.last_login_ip = user_ip 177 | 178 | db.session.commit() 179 | 180 | flash('登录成功!', 'success') 181 | return response 182 | 183 | flash('用户名或密码错误!', 'danger') 184 | else: 185 | if user and check_password_hash(user.password, password): 186 | # 存储临时登录信息 187 | session['temp_user_id'] = user.id 188 | session['temp_remember'] = remember 189 | session['temp_email'] = user.email 190 | 191 | # 发送OTP验证码 192 | send_otp_email(user) 193 | 194 | return redirect(url_for('auth.verify_otp')) 195 | 196 | flash('用户名或密码错误!', 'danger') 197 | 198 | return render_template('auth/login.html',email=current_app.config.get('DOMAIN_EMAIL'),title=current_app.config.get('DOMAIN_NAME')) 199 | 200 | @bp.route('/verify-otp', methods=['GET', 'POST']) 201 | def verify_otp(): 202 | if 'temp_user_id' not in session: 203 | return redirect(url_for('auth.login')) 204 | 205 | user = User.query.get(session['temp_user_id']) 206 | if not user: 207 | flash('用户不存在!', 'danger') 208 | return redirect(url_for('auth.login')) 209 | 210 | if request.method == 'POST': 211 | otp_code = request.form.get('otp_code') 212 | remember = session.get('temp_remember') 213 | user_ip = get_user_ip() 214 | 215 | if verify_otp_code(user, otp_code): 216 | # 清除临时会话数据 217 | session.pop('temp_user_id', None) 218 | session.pop('temp_remember', None) 219 | 220 | # 设置登录会话 221 | session['username'] = user.username 222 | session['user_id'] = user.id 223 | session['role'] = user.role 224 | 225 | # 处理"记住我" 226 | response = make_response(redirect(url_for('main.index'))) 227 | if remember: 228 | remember_token = generate_remember_token(user.id) 229 | response.set_cookie( 230 | 'remember_token', 231 | remember_token, 232 | max_age=60 * 60 * 24 * current_app.config.get('JWT_EXPIRATION_DAYS', 30), 233 | httponly=True, 234 | secure=current_app.config.get('COOKIE_SECURE') 235 | ) 236 | 237 | # 更新最后登录时间 238 | user.last_login = datetime.utcnow() 239 | # 记录本次登录ip 240 | user.last_login_ip = user_ip 241 | user.otp_code = None 242 | user.otp_expiration =None 243 | db.session.commit() 244 | 245 | flash('登录成功!', 'success') 246 | return response 247 | 248 | flash('验证码错误或已过期!', 'danger') 249 | 250 | return render_template('auth/verify_otp.html',email=current_app.config.get('DOMAIN_EMAIL'),title=current_app.config.get('DOMAIN_NAME')) 251 | 252 | @bp.route('/resend-otp', methods=['POST']) 253 | def resend_otp(): 254 | if 'temp_user_id' not in session: 255 | return jsonify({'error': 'Invalid session'}), 400 256 | 257 | user = User.query.get(session['temp_user_id']) 258 | if not user: 259 | return jsonify({'error': 'User not found'}), 404 260 | 261 | send_otp_email(user) 262 | return jsonify({'message': 'OTP sent successfully'}), 200 263 | 264 | 265 | reset_tokens = {} 266 | 267 | @bp.route('/forgot-password', methods=['GET', 'POST']) 268 | def forgot_password(): 269 | if 'username' in session: 270 | return redirect(url_for('main.index')) 271 | 272 | if request.method == 'POST': 273 | email = request.form.get('email') 274 | if not email: 275 | flash('请输入邮箱地址', 'danger') 276 | return redirect(url_for('auth.forgot_password')) 277 | 278 | # 检查邮箱是否存在 279 | user = User.query.filter_by(email=email).first() 280 | if not user: 281 | flash('该邮箱地址未注册', 'danger') 282 | return redirect(url_for('auth.forgot_password')) 283 | 284 | # 生成重置令牌并保存 285 | token = generate_reset_token() 286 | reset_tokens[token] = { 287 | 'email': email, 288 | 'expires_at': datetime.now() + timedelta(minutes=30) 289 | } 290 | 291 | # 生成重置链接 292 | reset_link = url_for('auth.reset_password', token=token, _external=True,email=current_app.config.get('DOMAIN_EMAIL'),title=current_app.config.get('DOMAIN_NAME')) 293 | 294 | # 发送重置邮件 295 | if send_reset_email(email, reset_link): 296 | flash('重置链接已发送到您的邮箱,请查收', 'success') 297 | return redirect(url_for('auth.login')) 298 | else: 299 | flash('发送邮件失败,请稍后重试', 'danger') 300 | return redirect(url_for('auth.forgot_password')) 301 | 302 | return render_template('auth/forgot_password.html') 303 | 304 | @bp.route('/reset-password/', methods=['GET', 'POST']) 305 | def reset_password(token): 306 | if 'username' in session: 307 | return redirect(url_for('main.index')) 308 | 309 | # 检查令牌是否存在 310 | if token not in reset_tokens: 311 | flash('重置链接无效或已过期', 'danger') 312 | return redirect(url_for('auth.forgot_password')) 313 | 314 | # 检查令牌是否过期 315 | token_data = reset_tokens[token] 316 | if datetime.now() > token_data['expires_at']: 317 | del reset_tokens[token] 318 | flash('重置链接已过期,请重新申请', 'danger') 319 | return redirect(url_for('auth.forgot_password')) 320 | 321 | if request.method == 'POST': 322 | new_password = request.form.get('new_password') 323 | confirm_password = request.form.get('confirm_password') 324 | 325 | if not all([new_password, confirm_password]): 326 | flash('请填写所有字段', 'danger') 327 | return redirect(url_for('auth.reset_password', token=token)) 328 | 329 | # 验证密码 330 | if new_password != confirm_password: 331 | flash('两次输入的密码不匹配', 'danger') 332 | return redirect(url_for('auth.reset_password', token=token)) 333 | 334 | # 更新密码 335 | user = User.query.filter_by(email=token_data['email']).first() 336 | user.password = generate_password_hash(new_password) 337 | db.session.commit() 338 | 339 | # 清除令牌 340 | del reset_tokens[token] 341 | 342 | flash('密码重置成功,请使用新密码登录', 'success') 343 | return redirect(url_for('auth.login')) 344 | 345 | return render_template('auth/reset_password.html', token=token,email=current_app.config.get('DOMAIN_EMAIL'),title=current_app.config.get('DOMAIN_NAME')) 346 | 347 | 348 | @bp.route('/logout') 349 | def logout(): 350 | session.clear() 351 | response = make_response(redirect(url_for('auth.login'))) 352 | response.delete_cookie('remember_token') 353 | flash('已成功退出登录!', 'info') 354 | return response -------------------------------------------------------------------------------- /flasks/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {% block title %}{% endblock %} 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 283 | 284 | 285 | 286 | 287 | 351 | 352 | 353 |
354 | {% with messages = get_flashed_messages(with_categories=true) %} 355 | {% if messages %} 356 | {% for category, message in messages %} 357 | 363 | {% endfor %} 364 | {% endif %} 365 | {% endwith %} 366 |
367 | 368 |
369 | 370 |
371 | {% block content %}{% endblock %} 372 |
373 |
374 | 375 |
376 |
377 |
378 |

© 2024 ChatGPT共享. All Rights Reserved. Created by bocchi2b

381 |

382 | 联系我们: {{email}} 384 | | 385 | Version 1.2.0 386 |

387 |
388 |
389 |
390 | 391 | 392 | 415 | 416 | 417 | 418 | 419 | -------------------------------------------------------------------------------- /flasks/templates/admin/claude.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}ChatGPT账号管理{% endblock %} 4 | 5 | {% block content %} 6 | 99 |
100 |

账号列表 101 | 105 |

106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 |
EmailSk Token状态操作
117 |
118 | 124 |

125 |
126 |
127 | 128 | 175 | 209 | 210 | 211 | 336 | 382 | {% endblock %} -------------------------------------------------------------------------------- /flasks/blueprints/admin.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, current_app, render_template, request, jsonify 2 | from sqlalchemy import func 3 | from ..models import AutoRefresh, ChatToken, InvitationCodes, Record, User, db 4 | from ..utils import admin_required, generate_verification_code, login_required,is_valid_email, refresh_access_tokens, schedule_next_refresh, usage_record 5 | import psutil 6 | from werkzeug.security import generate_password_hash 7 | from datetime import datetime, timedelta 8 | import platform 9 | 10 | 11 | bp = Blueprint('admin', __name__) 12 | 13 | 14 | # 分页与搜索函数 15 | def get_paginated_users(page=1, per_page=5, search=''): 16 | """ 17 | 获取分页的用户列表,并支持按用户名和邮箱搜索。 18 | """ 19 | page = request.args.get('page', page, type=int) 20 | search = request.args.get('search', search, type=str).strip().lower() # 获取搜索关键字 21 | 22 | query = User.query.filter() 23 | 24 | # 如果存在搜索条件,进行用户名或邮箱模糊匹配 25 | if search: 26 | query = query.filter( 27 | (User.username.ilike(f'%{search}%')) | 28 | (User.email.ilike(f'%{search}%')) 29 | ) 30 | 31 | query = query.order_by(User.id) # 排序 32 | pagination = query.paginate(page=page, per_page=per_page, error_out=False) 33 | 34 | # 计算当前页显示的记录范围 35 | start_index = (page - 1) * per_page + 1 36 | end_index = min(page * per_page, pagination.total) 37 | 38 | pagination_info = { 39 | 'total': pagination.total, 40 | 'pages': pagination.pages, 41 | 'current_page': page, 42 | 'per_page': per_page, 43 | 'has_prev': pagination.has_prev, 44 | 'has_next': pagination.has_next, 45 | 'prev_num': pagination.prev_num, 46 | 'next_num': pagination.next_num, 47 | 'start_index': start_index, 48 | 'end_index': end_index 49 | } 50 | 51 | return pagination.items, pagination_info 52 | 53 | @bp.route('/admin') 54 | @login_required 55 | @admin_required 56 | def index(): 57 | return render_template('admin/admin_info.html',email=current_app.config.get('DOMAIN_EMAIL'),title=current_app.config.get('DOMAIN_NAME')) 58 | 59 | # 获取系统负载信息 60 | @bp.route('/system_load') 61 | @admin_required 62 | def system_load(): 63 | # 获取CPU、内存等信息 64 | cpu_percent = psutil.cpu_percent(interval=1) 65 | memory = psutil.virtual_memory() 66 | disk = psutil.disk_usage('/') 67 | 68 | # 系统版本信息 69 | system_version = platform.platform() 70 | 71 | # 上次更新时间,设为固定值 72 | last_update = "2024-10-01" # 固定的时间戳或描述字符串 73 | 74 | # 运行时长 75 | boot_time = datetime.fromtimestamp(psutil.boot_time()) 76 | current_time = datetime.now() 77 | uptime = str(current_time - boot_time) 78 | 79 | # 返回 JSON 格式的系统负载信息 80 | return jsonify({ 81 | 'cpu_percent': cpu_percent, 82 | 'memory_total': memory.total, 83 | 'memory_used': memory.used, 84 | 'memory_percent': memory.percent, 85 | 'disk_total': disk.total, 86 | 'disk_used': disk.used, 87 | 'disk_percent': disk.percent, 88 | 'system_version': system_version, 89 | 'last_update': last_update, 90 | 'uptime': uptime 91 | }) 92 | 93 | # 获取用户统计信息 94 | @bp.route('/user_stats') 95 | @admin_required 96 | def user_stats(): 97 | try: 98 | # 获取总用户数 99 | total_users = User.query.count() 100 | 101 | # 获取最近30天注册的用户数 102 | thirty_days_ago = datetime.utcnow() - timedelta(days=30) 103 | new_users_30d = User.query.filter(User.registered_at >= thirty_days_ago).count() 104 | 105 | # 获取最近7天注册的用户数 106 | seven_days_ago = datetime.utcnow() - timedelta(days=7) 107 | new_users_7d = User.query.filter(User.registered_at >= seven_days_ago).count() 108 | 109 | # 获取不同角色的用户数量 110 | role_counts = db.session.query( 111 | User.role, 112 | func.count(User.id) 113 | ).group_by(User.role).all() 114 | 115 | # 计算每个角色的用户数量 116 | role_stats = {role: count for role, count in role_counts} 117 | 118 | # 获取最近24小时内活跃(登录)的用户数 119 | last_24h = datetime.utcnow() - timedelta(hours=24) 120 | active_users_24h = User.query.filter(User.last_login >= last_24h).count() 121 | 122 | return jsonify({ 123 | 'total_users': total_users, 124 | 'new_users_30d': new_users_30d, 125 | 'new_users_7d': new_users_7d, 126 | 'role_distribution': role_stats, 127 | 'active_users_24h': active_users_24h, 128 | 'growth_rate': { 129 | 'monthly': round((new_users_30d / total_users * 100), 1) if total_users > 0 else 0, 130 | 'weekly': round((new_users_7d / total_users * 100), 1) if total_users > 0 else 0 131 | }, 132 | 'last_updated': datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S') 133 | }) 134 | 135 | except Exception as e: 136 | return jsonify({ 137 | 'error': 'Failed to fetch user statistics', 138 | 'message': str(e) 139 | }), 500 140 | 141 | # 最近用户 142 | @bp.route('/api/users/recent', methods=['GET']) 143 | @admin_required 144 | def get_recent_users(): 145 | users = User.query.order_by(User.registered_at.desc()).limit(5).all() 146 | return jsonify([{ 147 | 'id': user.id, 148 | 'username': user.username, 149 | 'email': user.email, 150 | 'registered_at': user.registered_at.strftime('%Y-%m-%d'), 151 | 'is_active': bool(user.last_login and (datetime.utcnow() - user.last_login).days < 7) 152 | } for user in users]) 153 | 154 | # 添加用户 155 | @bp.route('/api/users', methods=['POST']) 156 | @admin_required 157 | def add_user(): 158 | data = request.json 159 | 160 | # 验证请求数据的存在性 161 | required_fields = ['username', 'email', 'password'] 162 | for field in required_fields: 163 | if field not in data: 164 | return jsonify({'status': 'error', 'message': f'{field} 字段缺失'}), 400 165 | 166 | # 检查用户名和邮箱的唯一性 167 | if User.query.filter_by(username=data['username']).first(): 168 | return jsonify({'status': 'error', 'message': '用户名已存在'}), 400 169 | if User.query.filter_by(email=data['email']).first(): 170 | return jsonify({'status': 'error', 'message': '邮箱已存在'}), 400 171 | # 验证邮箱可用性 172 | if not is_valid_email(data['email']): 173 | return jsonify({'status': 'error', 'message': '邮箱不可用'}), 400 174 | 175 | try: 176 | # 创建用户实例 177 | user = User( 178 | username=data['username'], 179 | email=data['email'], 180 | password=generate_password_hash(data['password']), 181 | role=data.get('role', 'user'), # 默认角色为 'user' 182 | registered_at=datetime.utcnow() 183 | ) 184 | 185 | # 添加到数据库 186 | db.session.add(user) 187 | db.session.commit() 188 | 189 | # 返回成功的响应 190 | return jsonify({ 191 | 'status': 'success', 192 | 'user': { 193 | 'id': user.id, 194 | 'username': user.username, 195 | 'email': user.email, 196 | 'registered_at': user.registered_at.strftime('%Y-%m-%d'), 197 | 'is_active': True 198 | } 199 | }), 201 200 | 201 | except Exception as e: 202 | # 出现异常时回滚 203 | db.session.rollback() 204 | return jsonify({'status': 'error', 'message': str(e)}), 400 205 | 206 | @bp.route('/api/code', methods=['POST', 'GET']) 207 | @login_required 208 | @admin_required 209 | def code(): 210 | if request.method == 'POST': 211 | # 处理生成新的邀请码 212 | code = generate_verification_code() 213 | 214 | while True: 215 | if InvitationCodes.query.filter_by(code=code).first(): 216 | # 如果邀请码已存在,重新生成邀请码 217 | code = generate_verification_code() 218 | else: 219 | # 如果邀请码不重复,创建新邀请码并添加到数据库 220 | newCode = InvitationCodes(code=code) 221 | db.session.add(newCode) 222 | db.session.commit() 223 | return jsonify({'message': '邀请码生成成功', 'code': code}), 201 224 | 225 | elif request.method == 'GET': 226 | # 处理返回邀请码列表 227 | codes = InvitationCodes.query.all() 228 | code_list = [code_entry.code for code_entry in codes] 229 | return jsonify({'codes': code_list}), 200 230 | 231 | return jsonify({'error': '不支持的请求方法'}), 405 # 如果请求方法不是 GET 或 POST 232 | 233 | 234 | 235 | 236 | 237 | # 管理员-用户管理面板路由 238 | @bp.route('/admin_user') 239 | @login_required 240 | @admin_required 241 | def admin_panel(): 242 | page = request.args.get('page', 1, type=int) 243 | per_page = request.args.get('per_page', 5, type=int) 244 | search = request.args.get('search', '', type=str) # 获取搜索参数 245 | 246 | users, pagination = get_paginated_users(page, per_page, search) 247 | 248 | 249 | # 获取当前时间 250 | now = datetime.utcnow() 251 | 252 | # 计算15天前的日期 253 | seven_days_ago = now - timedelta(days=15) 254 | 255 | # 基础统计 256 | total_users = User.query.count() 257 | new_users_count = User.query.filter(User.registered_at >= seven_days_ago).count() 258 | active_users_count = User.query.filter(User.last_login >= seven_days_ago).count() 259 | inactive_users_count = total_users - active_users_count 260 | 261 | # 获取近15天的日期列表 262 | dates = [(now - timedelta(days=i)).strftime('%m-%d') for i in range(14, -1, -1)] 263 | 264 | # 获取每天的注册用户数 265 | registration_data = [] 266 | activity_data = [] 267 | 268 | for i in range(14, -1, -1): 269 | date = (now - timedelta(days=i)).date() 270 | next_date = date + timedelta(days=1) 271 | 272 | # 统计注册用户 273 | reg_count = User.query.filter( 274 | User.registered_at >= date, 275 | User.registered_at < next_date 276 | ).count() 277 | registration_data.append(reg_count) 278 | 279 | # 统计活跃用户 280 | active_count = User.query.filter( 281 | User.last_login >= date, 282 | User.last_login < next_date 283 | ).count() 284 | activity_data.append(active_count) 285 | 286 | # 检查是否为 AJAX 请求 287 | if request.headers.get('X-Requested-With') == 'XMLHttpRequest': 288 | # 如果是 AJAX 请求,只返回表格和分页部分 289 | return render_template('admin/_user_table.html', users=users, pagination=pagination,email=current_app.config.get('DOMAIN_EMAIL'),title=current_app.config.get('DOMAIN_NAME')) 290 | 291 | # 正常请求返回完整页面 292 | return render_template('admin/admin_user.html', 293 | users=users, pagination=pagination, 294 | total_users=total_users, 295 | new_users_count=new_users_count, 296 | active_users_count=active_users_count, 297 | inactive_users_count=inactive_users_count, 298 | dates=dates, 299 | registration_data=registration_data, 300 | activity_data=activity_data, 301 | now=now, 302 | email=current_app.config.get('DOMAIN_EMAIL'), 303 | title=current_app.config.get('DOMAIN_NAME')) 304 | 305 | # 更新用户路由 306 | @bp.route('/api/users/', methods=['PUT']) 307 | @admin_required 308 | def update_user(user_id): 309 | user = User.query.get_or_404(user_id) 310 | data = request.get_json() 311 | 312 | try: 313 | # 检查用户名是否已存在(排除当前用户) 314 | if 'username' in data and data['username'] != user.username: 315 | existing_user = User.query.filter_by(username=data['username']).first() 316 | if existing_user: 317 | return jsonify({'error': '用户名已存在'}), 400 318 | user.username = data['username'] 319 | 320 | # 检查邮箱是否已存在(排除当前用户) 321 | if 'email' in data and data['email'] != user.email: 322 | existing_email = User.query.filter_by(email=data['email']).first() 323 | if existing_email: 324 | return jsonify({'error': '邮箱已存在'}), 400 325 | # 验证邮箱可用性 326 | if not is_valid_email(data['email']): 327 | return jsonify({'error': '邮箱不可用'}), 400 328 | user.email = data['email'] 329 | 330 | # 更新角色 331 | if 'role' in data: 332 | user.role = data['role'] 333 | 334 | # 如果提供了新密码,则更新密码 335 | if 'password' in data and data['password']: 336 | user.password = generate_password_hash(data['password']) 337 | 338 | db.session.commit() 339 | return jsonify({'message': '用户更新成功'}), 200 340 | except Exception as e: 341 | db.session.rollback() 342 | return jsonify({'error': str(e)}), 400 343 | 344 | # 删除用户路由 345 | @bp.route('/api/users/', methods=['DELETE']) 346 | @admin_required 347 | def delete_user(user_id): 348 | user = User.query.get_or_404(user_id) 349 | 350 | try: 351 | db.session.delete(user) 352 | db.session.commit() 353 | return jsonify({'message': '用户删除成功'}), 200 354 | except Exception as e: 355 | db.session.rollback() 356 | return jsonify({'error': str(e)}), 400 357 | 358 | 359 | # 用量表信息 360 | @bp.route('/api/record_info/') 361 | @admin_required 362 | def record_value(value): 363 | try: 364 | 365 | values = Record.query.all() 366 | if value == 'email': 367 | value_list = [value.to_dict_email() for value in values] 368 | elif value == 'user': 369 | value_list = [value.to_dict_user() for value in values] 370 | # 使用集合去重 371 | unique_values = [] 372 | seen_values = set() 373 | 374 | for info in value_list: 375 | if value == 'email': 376 | if info['email'] not in seen_values: 377 | unique_values.append(info) 378 | seen_values.add(info['email']) 379 | elif value == 'user': 380 | if info['username'] not in seen_values: 381 | unique_values.append(info) 382 | seen_values.add(info['username']) 383 | 384 | return jsonify(unique_values), 200 385 | 386 | except FileExistsError: 387 | return jsonify([]), 200 388 | 389 | # 用量查询 390 | 391 | @bp.route('/api/record//') 392 | @admin_required 393 | def record(type,value): 394 | statistics = None 395 | gpt35_limit = 0 396 | gpt4_limit = 0 397 | gpt4o_limit = 0 398 | gpt4o_mini_limit = 0 399 | o1_limit = 0 400 | o1_mini_limit = 0 401 | auto = 0 402 | if type == 'All': 403 | statistics = Record.query.all() 404 | else: 405 | if type == 'email': 406 | statistics = Record.query.filter_by(email = value).all() 407 | elif type == 'username': 408 | statistics = Record.query.filter_by(username = value).all() 409 | 410 | if statistics: 411 | for statistic in statistics: 412 | at = statistic.access_token 413 | st = statistic.shared_token 414 | result = usage_record(at,st) 415 | if result.get("usage"): 416 | if result["usage"].get("auto"): 417 | auto = auto + int(result["usage"]["auto"]) 418 | if result.get("gpt35_limit"): 419 | gpt35_limit = gpt35_limit + int(result['gpt35_limit']) 420 | if result.get("gpt4_limit"): 421 | gpt4_limit = gpt4_limit + int(result['gpt4_limit']) 422 | if result.get("gpt4o_limit"): 423 | gpt4o_limit = gpt4o_limit + int(result['gpt4o_limit']) 424 | if result.get("gpt4_limit"): 425 | gpt4o_mini_limit = gpt4o_mini_limit + int(result['gpt4o_mini_limit']) 426 | if result.get("o1_limit"): 427 | o1_limit = o1_limit + int(result['o1_limit']) 428 | if result.get("o1_mini_limit"): 429 | o1_mini_limit = o1_mini_limit + int(result['o1_mini_limit']) 430 | return {"auto":auto, 431 | "gpt3.5":gpt35_limit, 432 | "gpt4":gpt4_limit, 433 | "gpt4o":gpt4o_limit, 434 | "gpt4o-mini":gpt4o_mini_limit, 435 | "o1":o1_limit, 436 | "o1-mini":o1_mini_limit} 437 | 438 | -------------------------------------------------------------------------------- /flasks/templates/admin/chat.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}ChatGPT账号管理{% endblock %} 4 | 5 | {% block content %} 6 | 99 |
100 |

账号列表 101 | 105 |

106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 |
EmailRe Token状态操作
119 |
120 |
121 | 125 |
126 |

127 |
128 |
129 | 130 |
131 |

Access Token刷新设置

132 |
133 | 134 | 135 | 136 | 137 |
138 |

139 |
140 | 141 | 179 | 180 | 181 | 312 | 457 | {% endblock %} -------------------------------------------------------------------------------- /flasks/utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | import secrets 3 | import threading 4 | import time 5 | from flask import current_app, request, redirect, url_for, flash, session 6 | from email_validator import validate_email, EmailNotValidError 7 | from functools import wraps 8 | from datetime import datetime, timedelta 9 | import random 10 | import string 11 | import jwt 12 | import requests 13 | from .models import AutoRefresh, ChatToken, Record, User, db 14 | 15 | def login_required(f): 16 | """登录状态装饰器""" 17 | @wraps(f) 18 | def decorated_function(*args, **kwargs): 19 | if 'username' not in session: 20 | remember_token = request.cookies.get('remember_token') 21 | if remember_token: 22 | user_id = verify_remember_token(remember_token) 23 | if user_id: 24 | user = User.query.get(user_id) 25 | if user: 26 | # 重新设置session 27 | session['username'] = user.username 28 | session['user_id'] = user.id 29 | session['role'] = user.role 30 | return f(*args, **kwargs) 31 | flash('请先登录!', 'warning') 32 | return redirect(url_for('auth.login', next=request.url)) 33 | return f(*args, **kwargs) 34 | return decorated_function 35 | 36 | def admin_required(f): 37 | """管理员权限装饰器""" 38 | @wraps(f) 39 | def decorated_function(*args, **kwargs): 40 | if 'username' not in session: 41 | flash('请先登录!', 'warning') 42 | return redirect(url_for('auth.login', next=request.url)) 43 | 44 | user = User.query.filter_by(username=session['username']).first() 45 | if not user or user.role != 'admin': 46 | flash('需要管理员权限!', 'danger') 47 | return redirect(url_for('main.index')) 48 | return f(*args, **kwargs) 49 | return decorated_function 50 | 51 | # 邮箱验证函数 52 | def is_valid_email(email): 53 | try: 54 | # 验证邮箱格式和域名 55 | validate_email(email) 56 | return True 57 | except EmailNotValidError as e: 58 | return False 59 | 60 | # 邮件api配置 61 | 62 | def generate_verification_code(length=6): 63 | """生成指定长度的验证码""" 64 | return ''.join(random.choices(string.digits, k=length)) 65 | 66 | def send_verification_email(to_email, code): 67 | """发送验证码邮件""" 68 | send_body = { 69 | "from_name": "Bocchi2B", # 替换为你的发件人名称 70 | "to_name": "用户", 71 | "to_mail": to_email, 72 | "subject": "注册验证码", 73 | "is_html": True, 74 | "content": f""" 75 |
76 |

验证码

77 |

您好!

78 |

您的验证码是:

79 |
80 | {code} 81 |
82 |

验证码有效期为5分钟。如果不是您本人的操作,请忽略此邮件。

83 |

此邮件为系统自动发送,请勿回复。

84 |
85 | """ 86 | } 87 | 88 | try: 89 | response = requests.post( 90 | url=current_app.config.get('EMAIL_API'), 91 | json=send_body, 92 | headers={ 93 | "Authorization": f"Bearer {current_app.config.get('EMAIL_JWT')}", 94 | "Content-Type": "application/json" 95 | } 96 | ) 97 | return response.status_code == 200 98 | except Exception as e: 99 | print(f"发送邮件失败:{str(e)}") 100 | return False 101 | 102 | def send_otp_code_email(to_email, code): 103 | """发送验证码邮件""" 104 | send_body = { 105 | "from_name": "Bocchi2B", # 替换为你的发件人名称 106 | "to_name": "用户", 107 | "to_mail": to_email, 108 | "subject": "登录验证码", 109 | "is_html": True, 110 | "content": f""" 111 |
112 |

验证码

113 |

您好!

114 |

您的登录验证码是:

115 |
116 | {code} 117 |
118 |

验证码有效期为5分钟。如果不是您本人的操作,请尽快更改您的密码。

119 |

此邮件为系统自动发送,请勿回复。

120 |
121 | """ 122 | } 123 | 124 | try: 125 | response = requests.post( 126 | url=current_app.config.get('EMAIL_API'), 127 | json=send_body, 128 | headers={ 129 | "Authorization": f"Bearer {current_app.config.get('EMAIL_JWT')}", 130 | "Content-Type": "application/json" 131 | } 132 | ) 133 | return response.status_code == 200 134 | except Exception as e: 135 | print(f"发送邮件失败:{str(e)}") 136 | return False 137 | 138 | 139 | def verify_code(email, code): 140 | """验证验证码是否正确""" 141 | stored_code = session.get(f'verification_code_{email}') 142 | expire_time = session.get(f'code_expire_time_{email}') 143 | 144 | if not stored_code or not expire_time: 145 | return False 146 | 147 | expire_time = datetime.fromisoformat(expire_time) 148 | if datetime.now() > expire_time: 149 | # 清除过期的验证码 150 | session.pop(f'verification_code_{email}', None) 151 | session.pop(f'code_expire_time_{email}', None) 152 | return False 153 | 154 | return stored_code == code 155 | 156 | def generate_reset_token(): 157 | """生成安全的重置令牌""" 158 | return secrets.token_urlsafe(32) 159 | 160 | def send_reset_email(to_email, reset_link): 161 | """发送密码重置链接邮件""" 162 | send_body = { 163 | "from_name": "Bocchi2B", # 替换为你的发件人名称 164 | "to_name": "用户", 165 | "to_mail": to_email, 166 | "subject": "密码重置", 167 | "is_html": True, 168 | "content": f""" 169 |
170 |

密码重置

171 |

您好!

172 |

我们收到了您的密码重置请求。请点击下面的链接重置您的密码:

173 | 180 |

此链接将在30分钟后过期。如果不是您本人的操作,请忽略此邮件。

181 |

182 | 如果按钮无法点击,请复制以下链接到浏览器地址栏:
183 | {reset_link} 184 |

185 |

此邮件为系统自动发送,请勿回复。

186 |
187 | """ 188 | } 189 | 190 | try: 191 | response = requests.post( 192 | url=current_app.config.get('EMAIL_API'), 193 | json=send_body, 194 | headers={ 195 | "Authorization": f"Bearer {current_app.config.get('EMAIL_JWT')}", 196 | "Content-Type": "application/json" 197 | } 198 | ) 199 | return response.status_code == 200 200 | except Exception as e: 201 | print(f"发送邮件失败:{str(e)}") 202 | return False 203 | 204 | # 记录用户登录ip 205 | def get_user_ip(): 206 | # 尝试从请求头中获取 X-Forwarded-For 207 | if request.headers.getlist("X-Forwarded-For"): 208 | ip = request.headers.getlist("X-Forwarded-For")[0] 209 | else: 210 | ip = request.remote_addr 211 | return ip 212 | 213 | def generate_remember_token(user_id): 214 | """生成记住我令牌""" 215 | payload = { 216 | 'user_id': user_id, 217 | 'exp': datetime.utcnow() + timedelta(days=current_app.config.get('JWT_EXPIRATION_DAYS',30)) 218 | } 219 | token = jwt.encode(payload, current_app.config['SECRET_KEY'], algorithm='HS256') 220 | 221 | # 如果 token 是字节对象,则解码为字符串 222 | if isinstance(token, bytes): 223 | token = token.decode('utf-8') 224 | 225 | return token 226 | 227 | def verify_remember_token(token): 228 | """验证记住我令牌""" 229 | try: 230 | payload = jwt.decode(token, current_app.config['SECRET_KEY'], algorithms=['HS256']) 231 | return payload['user_id'] 232 | except: 233 | return None 234 | 235 | 236 | # 两步验证 237 | def send_otp_email(user): 238 | otp_code = generate_verification_code() # 生成一个随机OTP 239 | user.otp_code = otp_code 240 | user.otp_expiration = datetime.utcnow() + timedelta(minutes=5) # 5分钟有效期 241 | db.session.commit() 242 | # 使用邮件库发送OTP到用户邮箱 243 | send_otp_code_email(user.email, otp_code) 244 | 245 | def verify_otp_code(user, otp_code): 246 | """验证OTP码是否有效 247 | 248 | Args: 249 | user: User对象,包含otp_code和otp_expiration属性 250 | otp_code: 用户输入的OTP验证码 251 | 252 | Returns: 253 | bool: 验证是否成功 254 | """ 255 | if not user.otp_code or not user.otp_expiration: 256 | return False 257 | 258 | if datetime.utcnow() > user.otp_expiration: 259 | return False 260 | 261 | return user.otp_code == otp_code 262 | 263 | 264 | # 刷新 access_token 的主函数 265 | def refresh_access_tokens(): 266 | # 读取 refresh_token 列表 267 | refresh_tokens_list = ChatToken.query.all() 268 | refresh_tokens = [token.to_dict() for token in refresh_tokens_list] 269 | 270 | # 遍历 refresh_token 列表 271 | for token_info in refresh_tokens: 272 | 273 | token = ChatToken.query.filter(ChatToken.email == token_info['email']).first() 274 | 275 | refresh_token = token.refresh_token 276 | 277 | # 如果 refresh_token 为空,跳过这一行 278 | if not refresh_token: 279 | continue 280 | 281 | # 先清除用量表中的access_token 282 | records_to_delete = Record.query.filter_by(access_token=token.access_token).all() 283 | for record in records_to_delete: 284 | db.session.delete(record) 285 | db.session.commit() 286 | 287 | try: 288 | # 使用 POST 请求通过 refresh_token 获取 access_token 289 | response = requests.post( 290 | "https://token.oaifree.com/api/auth/refresh", 291 | data={"refresh_token": refresh_token} 292 | ) 293 | response_data = response.json() 294 | 295 | access_token = response_data.get("access_token") 296 | if access_token: # 如果成功获取到 access_token 297 | # 更新 access_token 和状态为 True 298 | token.access_token = access_token 299 | token.status = True 300 | else: 301 | # 如果获取失败,设置状态为 False 302 | token.status = False 303 | 304 | db.session.commit() 305 | except Exception as e: 306 | # 捕获请求错误并记录失败的 token,状态为 False 307 | token.status = False 308 | db.session.commit() 309 | 310 | return refresh_tokens 311 | 312 | # 获取share token 313 | def register_token(access_token, unique_name, expire_in=0, show_userinfo=True, gpt35_limit=-1, 314 | gpt4_limit=-1, reset_limit=False, show_conversations=False, site_limit="", 315 | temporary_chat=False): 316 | """ 317 | 注册共享令牌的函数。 318 | 319 | :param access_token: 用户的访问令牌 320 | :param unique_name: 独一无二的共享令牌名称 321 | :param expire_in: 共享令牌的过期秒数,默认为0 322 | :param show_userinfo: 是否显示用户信息,默认为False 323 | :param gpt35_limit: GPT-3.5模型的使用限制,默认为-1表示不限制 324 | :param gpt4_limit: GPT-4模型的使用限制,默认为-1表示不限制 325 | :param reset_limit: 是否重置使用限制,默认为False 326 | :param show_conversations: 是否显示对话记录,默认为True 327 | :param site_limit: 站点使用限制,默认为空字符串,表示不限制 328 | :param temporary_chat: 是否开启临时聊天功能,默认为True 329 | 330 | :return: token_key (str),注册成功后返回的共享令牌 key 331 | """ 332 | 333 | url = 'https://chat.oaifree.com/token/register' 334 | 335 | # 数据 payload 336 | data = { 337 | "access_token": access_token, 338 | "unique_name": unique_name, 339 | "expire_in": expire_in, 340 | "show_userinfo": show_userinfo, 341 | "gpt35_limit": gpt35_limit, 342 | "gpt4_limit": gpt4_limit, 343 | "reset_limit": reset_limit, 344 | "show_conversations": show_conversations, 345 | "site_limit": site_limit, 346 | "temporary_chat": temporary_chat 347 | } 348 | 349 | # 发起 POST 请求 350 | response = requests.post(url, headers={'Content-Type': 'application/x-www-form-urlencoded'}, data=data) 351 | 352 | # 获取返回的共享令牌 key 353 | token_key = response.json().get("token_key") 354 | 355 | return token_key 356 | 357 | # 获取用量信息 358 | def usage_record(at,st): 359 | # 构建URL 360 | base_url = "https://chat.oaifree.com/token/info/" 361 | url = f"{base_url}{st}" 362 | 363 | # 设置请求头 364 | headers = { 365 | "Authorization": f"Bearer {at}", 366 | "User-Agent": "Mozilla/5.0", 367 | "Accept": "application/json" 368 | } 369 | 370 | try: 371 | # 发送GET请求 372 | response = requests.get(url, headers=headers) 373 | 374 | # 检查响应状态码 375 | response.raise_for_status() 376 | 377 | # 返回JSON响应 378 | return response.json() 379 | 380 | except requests.exceptions.RequestException as e: 381 | # print(f"请求发生错误: {e}") 382 | return None 383 | 384 | # vv佬PLUS识别 385 | def check_final_url(url, target_url): 386 | try: 387 | # 发送 GET 请求,允许重定向 388 | response = requests.get(url, allow_redirects=True) 389 | 390 | # 获取最终 URL 391 | final_url = response.url 392 | 393 | # 检查最终 URL 是否匹配目标地址 394 | if final_url != target_url: 395 | 396 | return True # 如果不匹配,返回原始链接 397 | else: 398 | return None # 如果匹配,返回 None 399 | except requests.exceptions.RequestException: 400 | return None # 请求失败,返回 None 401 | 402 | # 获取登陆链接 403 | def getoauth(token): 404 | domain = current_app.config.get('DOMAIN_CHATGPT') 405 | print(domain) 406 | share_token = token 407 | 408 | url = f'https://{domain}/api/auth/oauth_token' 409 | headers = { 410 | 'Origin': f'https://{domain}', 411 | 'Content-Type': 'application/json' 412 | } 413 | data = { 414 | 'share_token': share_token 415 | } 416 | try: 417 | response = requests.post(url, headers=headers, json=data) 418 | loginurl = response.json().get('login_url') 419 | return loginurl 420 | except requests.RequestException as e: 421 | return None 422 | 423 | 424 | # 定时刷新token 425 | 426 | def is_main_process(): 427 | import os 428 | return os.environ.get('WERKZEUG_RUN_MAIN') != 'true' 429 | 430 | current_timer = None 431 | timer_lock = threading.Lock() 432 | 433 | def schedule_next_refresh(): 434 | 435 | global current_timer 436 | config = AutoRefresh.query.filter_by(id=1).first() 437 | 438 | with timer_lock: 439 | if config.auto_refresh_enabled: 440 | if current_timer: 441 | current_timer.cancel() 442 | 443 | next_refresh = datetime.now() + timedelta(days=config.refresh_interval_days) 444 | config.next_refresh_time = next_refresh.isoformat() 445 | db.session.commit() 446 | 447 | current_timer = threading.Timer( 448 | (next_refresh - datetime.now()).total_seconds(), 449 | auto_refresh_tokens 450 | ) 451 | current_timer.start() 452 | 453 | def auto_refresh_tokens(): 454 | 455 | print('开始自动刷新') 456 | refresh_access_tokens() 457 | 458 | # 添加延时,确保两次刷新之间有足够间隔 459 | time.sleep(2) # 等待1秒 460 | 461 | # 刷新完成后,调度下一次刷新 462 | schedule_next_refresh() 463 | 464 | # 在应用启动时调用这个函数 465 | def init_auto_refresh(): 466 | if not is_main_process(): 467 | print("在 reloader 进程中,跳过定时器初始化") 468 | return 469 | 470 | print(f"在主进程中初始化自动刷新, 当前时间: {datetime.now()}") 471 | config = AutoRefresh.query.filter_by(id=1).first() 472 | 473 | if config.auto_refresh_enabled and config.next_refresh_time: 474 | next_refresh = datetime.fromisoformat(config.next_refresh_time) 475 | 476 | if next_refresh > datetime.now(): 477 | delay_seconds = (next_refresh - datetime.now()).total_seconds() 478 | print(f"设置初始定时器, 延迟秒数: {delay_seconds}") 479 | 480 | global current_timer 481 | with timer_lock: 482 | current_timer = threading.Timer(delay_seconds, auto_refresh_tokens) 483 | current_timer.start() 484 | else: 485 | schedule_next_refresh() 486 | 487 | # claude登陆链接获取 488 | def get_claude_login_url(session_key,uname): 489 | domain = current_app.config.get('DOMAIN_CLAUDE') 490 | url = f'https://{domain}/manage-api/auth/oauth_token' 491 | 492 | # 请求体参数 493 | data = { 494 | 'session_key': session_key, 495 | 'unique_name': uname # 生成唯一标识符 496 | } 497 | 498 | # 设置请求头 499 | headers = {'Content-Type': 'application/json'} 500 | 501 | try: 502 | # 发送 POST 请求 503 | response = requests.post(url, headers=headers, data=json.dumps(data)) 504 | 505 | # 检查响应状态码是否为200 506 | if response.status_code == 200: 507 | response_data = response.json() 508 | 509 | # 检查 'login_url' 是否存在 510 | if 'login_url' in response_data: 511 | login_url = response_data['login_url'] 512 | 513 | # 如果URL没有以http开头,拼接基础URL 514 | if not login_url.startswith('http'): 515 | return f'https://{domain}' + login_url 516 | return login_url 517 | 518 | # 如果状态码不是200或login_url不存在,返回None 519 | return None 520 | 521 | except requests.RequestException as e: 522 | # 捕获异常并返回错误信息 523 | return None -------------------------------------------------------------------------------- /flasks/templates/admin/admin_user.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}管理页面{% endblock %} 4 | 5 | {% block content %} 6 | 88 | 89 |
90 |
91 |
92 |
93 |
94 |
96 | 97 |
98 |
99 |
总用户数
100 |

{{ total_users }}

101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
112 | 113 |
114 |
115 |
新增用户
116 |

{{ new_users_count }}

117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
128 | 129 |
130 |
131 |
活跃用户
132 |

{{ active_users_count }}

133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
144 | 145 |
146 |
147 |
不活跃用户
148 |

{{ inactive_users_count }}

149 |
150 |
151 |
152 |
153 |
154 |
155 | 156 | 157 |
158 |
159 |
160 |
161 |
近15天注册趋势
162 |

显示每日新增注册用户数量

163 | 164 |
165 |
166 |
167 |
168 |
169 |
170 |
用户活跃度分析
171 |

显示每日活跃用户数量

172 | 173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 | 181 | 182 |
183 |
184 |
185 |
186 |
187 |
邀请码管理
188 | 191 |
192 |
193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 |
邀请码剩余(个)操作
205 |
206 |
207 |
208 |
209 |
210 | 211 | 212 |
213 |
214 |
215 |
216 |
217 |
218 | 用户列表 219 |
220 |
221 | 222 |
223 |
224 | 226 | 230 |
231 |
232 |
233 |
234 | 235 |
236 | 237 |
238 | 239 | 246 | {% include "admin/_user_table.html" %} 247 |
248 |
249 |
250 | 251 | 252 | 300 | 301 | 302 | 320 |
321 | 322 | 323 | 398 | 399 | 525 | 708 | 709 | 710 | {% endblock %} --------------------------------------------------------------------------------