This commit is contained in:
zuowei1216
2025-12-30 14:46:22 +08:00
parent 6c73b31100
commit 12395d8eca
181 changed files with 1255 additions and 114 deletions

View File

@@ -79,23 +79,24 @@ if IS_DEV:
app.mount("/download", StaticFiles(directory="archives"), name="download")
# Mount Shell directory (登录页面)
shell_dir = Path(__file__).parent.parent / "Designer"
if shell_dir.exists():
app.mount("/shell", StaticFiles(directory=str(shell_dir), html=True), name="shell")
print(f"✓ Shell 已挂载 (Dev): {shell_dir}")
else:
print(f"⚠️ Shell 目录不存在: {shell_dir}")
print(" 请先运行: cd Designer && npm run build:shell")
# shell_dir = Path(__file__).parent.parent / "Designer"
# if shell_dir.exists():
# app.mount("/shell", StaticFiles(directory=str(shell_dir), html=True), name="shell")
# print(f"✓ Shell 已挂载 (Dev): {shell_dir}")
# else:
# # print(f"⚠️ Shell 目录不存在: {shell_dir}")
# # print(" 请先运行: cd Designer && npm run build:shell")
# pass
# Mount DesignerCache directory to serve Core application files
designer_cache = Path.home() / "AppData" / "Roaming" / "DesignerCache"
if designer_cache.exists():
app.mount("/core", StaticFiles(directory=str(designer_cache), html=True), name="core")
print(f"✓ Core 已挂载 (Dev): {designer_cache}")
else:
# Create directory if it doesn't exist
designer_cache.mkdir(parents=True, exist_ok=True)
app.mount("/core", StaticFiles(directory=str(designer_cache), html=True), name="core")
# designer_cache = Path.home() / "AppData" / "Roaming" / "DesignerCache"
# if designer_cache.exists():
# app.mount("/core", StaticFiles(directory=str(designer_cache), html=True), name="core")
# print(f"✓ Core 已挂载 (Dev): {designer_cache}")
# else:
# # Create directory if it doesn't exist
# designer_cache.mkdir(parents=True, exist_ok=True)
# app.mount("/core", StaticFiles(directory=str(designer_cache), html=True), name="core")
else:
print(" Production Mode: Static files are NOT mounted by FastAPI (handled by Caddy/Nginx).")

View File

@@ -1,69 +0,0 @@
import sys
import os
# Add current directory to sys.path
sys.path.append(os.getcwd())
from app.db import SessionLocal
from app.services.auth_service import auth_service
from app.schemas.auth import UserRegister
from app.core.security import get_password_hash
from app.models.user import User
def create_user(username, password, email=None):
db = SessionLocal()
try:
# Check if user exists
existing = db.query(User).filter(User.username == username).first()
if existing:
print(f"❌ 用户名 '{username}' 已存在")
return
# Prepare registration data
# We bypass the code verification by not providing code,
# but we set email if provided.
# However, auth_service.register sets is_verified=False by default if code is missing.
# We might want to manually set is_verified=True after registration for convenience.
register_data = UserRegister(
username=username,
password=password,
confirm_password=password,
email=email,
device_id="local_script"
)
# Call register service
try:
token = auth_service.register(db, register_data)
print(f"✅ 用户 '{username}' 注册成功!")
# Manually verify the user for local convenience
user = db.query(User).filter(User.username == username).first()
if user:
user.is_verified = True
user.permissions = "admin" # Grant admin permissions for local test user
db.commit()
print(f"✅ 已自动验证邮箱并赋予 admin 权限")
except Exception as e:
print(f"❌ 注册失败: {e}")
finally:
db.close()
if __name__ == "__main__":
if len(sys.path) < 2:
print("Usage: python create_user.py [username] [password]")
username = "admin"
password = "password123"
email = "admin@example.com"
if len(sys.argv) > 1:
username = sys.argv[1]
if len(sys.argv) > 2:
password = sys.argv[2]
print(f"正在创建用户: {username} ...")
create_user(username, password, email)

View File

@@ -1,120 +0,0 @@
# -*- coding: utf-8 -*-
"""
数据库初始化脚本
功能:
1. 检查数据库连接
2. 创建所有定义的表(如果不存在)
3. 检查现有表的字段,如果缺失则自动添加
"""
import os
import sys
import logging
from sqlalchemy import create_engine, inspect, text
from app.core.config import settings
from app.db import Base
# 导入所有模型以确保它们被注册到 Base.metadata
from app.models import user, group, business, session
# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def get_engine():
"""获取数据库引擎"""
# 优先使用环境变量中的配置,如果没有则构建
db_url = settings.DATABASE_URL
if not db_url:
host = os.getenv('DB_HOST', 'localhost')
port = os.getenv('DB_PORT', '3306')
user = os.getenv('DB_USER', 'root')
password = os.getenv('DB_PASSWORD', '')
db_name = os.getenv('DB_NAME', 'designer_db')
db_url = f"mysql+pymysql://{user}:{password}@{host}:{port}/{db_name}"
return create_engine(db_url)
def map_python_type_to_sql(col_type):
"""将 SQLAlchemy 类型映射为 MySQL 类型"""
type_str = str(col_type).lower()
if 'varchar' in type_str:
return type_str
if 'string' in type_str:
length = getattr(col_type, 'length', 255)
return f"varchar({length})"
if 'integer' in type_str or 'int' in type_str:
return "int"
if 'boolean' in type_str:
return "tinyint(1)"
if 'datetime' in type_str:
return "datetime"
if 'date' in type_str:
return "date"
if 'float' in type_str:
return "float"
if 'text' in type_str:
return "text"
return "varchar(255)" # 默认
def init_db():
logger.info("🔄 开始数据库初始化检查...")
try:
engine = get_engine()
inspector = inspect(engine)
# 1. 创建缺失的表
logger.info("📊 检查表结构...")
Base.metadata.create_all(bind=engine)
logger.info("✅ 基础表结构检查完成")
# 2. 检查并补充缺失的列
logger.info("🔍 检查缺失字段...")
existing_tables = inspector.get_table_names()
with engine.connect() as conn:
for table_name, table in Base.metadata.tables.items():
if table_name not in existing_tables:
continue
# 获取数据库中现有的列
existing_columns = [col['name'] for col in inspector.get_columns(table_name)]
# 检查模型定义的列
for column in table.columns:
if column.name not in existing_columns:
logger.info(f" 发现缺失字段: {table_name}.{column.name}")
# 构建 ALTER TABLE 语句
col_type = map_python_type_to_sql(column.type)
default_val = ""
# 处理默认值 (简化处理,只处理常见类型)
if column.default:
arg = column.default.arg
if isinstance(arg, (int, float, bool)):
if isinstance(arg, bool):
arg = 1 if arg else 0
default_val = f" DEFAULT {arg}"
elif isinstance(arg, str):
default_val = f" DEFAULT '{arg}'"
nullable = "NULL" if column.nullable else "NOT NULL"
if column.nullable and not default_val:
default_val = " DEFAULT NULL"
sql = f"ALTER TABLE {table_name} ADD COLUMN {column.name} {col_type} {nullable}{default_val};"
logger.info(f" 🚀 执行: {sql}")
conn.execute(text(sql))
conn.commit()
logger.info("✅ 数据库同步完成!")
except Exception as e:
logger.error(f"❌ 数据库初始化失败: {e}")
# 不抛出异常,以免阻断容器启动(如果是网络波动等临时问题)
# 但在生产环境中可能需要抛出
if __name__ == "__main__":
init_db()

View File

@@ -1,170 +0,0 @@
-- ==========================================
-- Database Initialization Script
-- Generated at: 2025-12-22 17:54:23.463367
-- ==========================================
-- 1. Select Database and Cleanup Tables
USE designer_db;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS check_in_config;
DROP TABLE IF EXISTS check_in_records;
DROP TABLE IF EXISTS features_config;
DROP TABLE IF EXISTS plugin_groups;
DROP TABLE IF EXISTS points_history;
DROP TABLE IF EXISTS vip_config;
DROP TABLE IF EXISTS users;
DROP TABLE IF EXISTS user_sessions;
SET FOREIGN_KEY_CHECKS = 1;
-- 2. Create Tables
CREATE TABLE check_in_config (
id INTEGER NOT NULL AUTO_INCREMENT,
consecutive_days INTEGER NOT NULL,
base_points INTEGER NOT NULL,
bonus_points INTEGER NOT NULL,
total_points INTEGER NOT NULL,
enabled BOOL,
created_at DATETIME DEFAULT now(),
updated_at DATETIME,
PRIMARY KEY (id),
UNIQUE (consecutive_days)
);
CREATE TABLE check_in_records (
id INTEGER NOT NULL AUTO_INCREMENT,
user_id INTEGER NOT NULL,
username VARCHAR(50) NOT NULL,
check_in_date DATE NOT NULL,
points_earned INTEGER NOT NULL,
consecutive_days INTEGER NOT NULL,
vip_multiplier FLOAT,
created_at DATETIME DEFAULT now(),
PRIMARY KEY (id)
);
CREATE TABLE features_config (
id INTEGER NOT NULL AUTO_INCREMENT,
feature_key VARCHAR(50) NOT NULL,
feature_name VARCHAR(100) NOT NULL,
category VARCHAR(50),
points_cost INTEGER,
vip_points_cost INTEGER,
svip_points_cost INTEGER,
enabled BOOL,
description TEXT,
created_at DATETIME DEFAULT now(),
updated_at DATETIME,
PRIMARY KEY (id)
);
CREATE TABLE plugin_groups (
id INTEGER NOT NULL AUTO_INCREMENT,
name VARCHAR(64) NOT NULL,
current_version_file VARCHAR(255),
comment TEXT,
PRIMARY KEY (id)
);
CREATE TABLE points_history (
id INTEGER NOT NULL AUTO_INCREMENT,
user_id INTEGER NOT NULL,
username VARCHAR(50) NOT NULL,
type VARCHAR(20) NOT NULL,
amount INTEGER NOT NULL,
balance INTEGER NOT NULL,
description VARCHAR(255),
created_at DATETIME DEFAULT now(),
PRIMARY KEY (id)
);
CREATE TABLE vip_config (
id INTEGER NOT NULL AUTO_INCREMENT,
vip_type VARCHAR(20) NOT NULL,
name VARCHAR(50) NOT NULL,
price FLOAT NOT NULL,
daily_quota INTEGER NOT NULL,
points_multiplier FLOAT,
enabled BOOL,
description TEXT,
created_at DATETIME DEFAULT now(),
updated_at DATETIME,
PRIMARY KEY (id),
UNIQUE (vip_type)
);
CREATE TABLE users (
id INTEGER NOT NULL AUTO_INCREMENT,
username VARCHAR(64) NOT NULL,
hashed_password VARCHAR(128) NOT NULL,
created_at DATETIME NOT NULL DEFAULT now(),
group_id INTEGER,
permissions TEXT,
expire_date DATETIME,
email VARCHAR(255),
is_verified BOOL,
verification_code VARCHAR(6),
reset_token VARCHAR(128),
reset_token_expire DATETIME,
nickname VARCHAR(50),
avatar VARCHAR(500),
points INTEGER,
level INTEGER,
vip_type VARCHAR(20),
vip_expire DATETIME,
vip_daily_quota INTEGER,
vip_quota_reset_date DATE,
total_check_in_days INTEGER,
consecutive_check_in INTEGER,
last_check_in_date DATE,
PRIMARY KEY (id),
FOREIGN KEY(group_id) REFERENCES plugin_groups (id)
);
CREATE TABLE user_sessions (
id INTEGER NOT NULL AUTO_INCREMENT,
user_id INTEGER NOT NULL,
device_id VARCHAR(128) NOT NULL,
active BOOL NOT NULL,
expires_at DATETIME,
created_at DATETIME NOT NULL DEFAULT now(),
login_at DATETIME,
logout_at DATETIME,
duration_seconds INTEGER,
last_seen_at DATETIME,
PRIMARY KEY (id),
FOREIGN KEY(user_id) REFERENCES users (id)
);
-- 3. Insert Initial Data
-- Default User Group
INSERT INTO plugin_groups (name, comment) VALUES ('default', 'Default User Group');
-- VIP Config
INSERT INTO vip_config (vip_type, name, price, daily_quota, points_multiplier) VALUES ('vip', 'VIP会员', 30.0, 20, 1.5);
INSERT INTO vip_config (vip_type, name, price, daily_quota, points_multiplier) VALUES ('svip', 'SVIP会员', 88.0, -1, 2.0);
-- Check-in Config
INSERT INTO checkin_config (consecutive_days, base_points, bonus_points, total_points) VALUES (1, 10, 0, 10);
INSERT INTO checkin_config (consecutive_days, base_points, bonus_points, total_points) VALUES (3, 10, 5, 15);
INSERT INTO checkin_config (consecutive_days, base_points, bonus_points, total_points) VALUES (7, 10, 20, 30);
-- Feature Config
INSERT INTO feature_configs (feature_key, feature_name, points_cost, category) VALUES ('ai_remove_bg', '智能抠图', 10, 'ai');
-- 4. Create Admin User (admin / password123)
INSERT INTO users (
username, hashed_password, email, is_verified, permissions,
group_id, nickname, level, vip_type, vip_expire,
created_at, points, total_check_in_days, consecutive_check_in, vip_daily_quota
) VALUES (
'admin',
'$2b$12$UsFjs3Jwn5BG7u/RJ1efNuTF4zIsjT.pSm1mQEBXGWKR.3Kakhpmq',
'admin@example.com',
1,
'admin,vip,svip',
(SELECT id FROM plugin_groups WHERE name = 'default' LIMIT 1),
'Administrator',
999,
'svip',
'2099-12-31 23:59:59',
NOW(),
0, 0, 0, 0
);

View File

@@ -1,292 +0,0 @@
# -*- coding: utf-8 -*-
"""
完整数据库初始化脚本 (Local Execution)
功能:
1. 重建数据库DROP & CREATE DATABASE
2. 创建所有表结构
3. 创建默认数据用户组、VIP配置、签到配置、功能配置
4. 创建默认管理员账户 (admin/password123)
注意:此脚本设计为在本地或 Docker 容器内运行,直接连接 MySQL。
它会读取环境变量或使用默认配置。
"""
import logging
import os
import sys
import argparse
from datetime import datetime
from sqlalchemy.schema import CreateTable
# 确保可以将当前目录添加到 sys.path
sys.path.append(os.getcwd())
from sqlalchemy import create_engine, text
from sqlalchemy.orm import sessionmaker
from sqlalchemy.engine.url import make_url
# 尝试导入应用模块
try:
from app.core.config import settings
from app.db import Base
from app.models import user, group, business, session
from app.models.user import User
from app.models.group import PluginGroup
from app.models.business import FeatureConfig, VipConfig, CheckInConfig
from app.core.security import get_password_hash
except ImportError:
print("❌ 无法导入应用模块,请确保在 Server 目录下运行此脚本")
print("示例: python scripts/init_full_db.py")
sys.exit(1)
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
def generate_sql_file():
"""生成完整的初始化 SQL 文件"""
logger.info("📝 正在生成 SQL 文件 (init_db.sql) ...")
# 获取数据库引擎 (用于编译 SQL)
# 我们使用一个 mock engine因为我们只想要 SQL 语句
engine = create_engine("mysql+pymysql://", strategy="mock", executor=lambda sql, *args, **kwargs: print(sql.compile(dialect=engine.dialect)))
# 真正的 engine 用于 dialect 编译
compile_engine = create_engine("mysql+pymysql://")
sql_content = []
# 1. 准备数据库
sql_content.append("-- ==========================================")
sql_content.append("-- Database Initialization Script")
sql_content.append(f"-- Generated at: {datetime.now()}")
sql_content.append("-- ==========================================\n")
# 既然 DROP DATABASE 被禁用,我们切换到目标数据库,并尝试删除所有表
sql_content.append("-- 1. Select Database and Cleanup Tables")
sql_content.append("USE designer_db;\n")
# 获取所有表名并生成 DROP TABLE 语句
# 注意:为了处理外键约束,我们先禁用外键检查
sql_content.append("SET FOREIGN_KEY_CHECKS = 0;")
for table in Base.metadata.sorted_tables:
sql_content.append(f"DROP TABLE IF EXISTS {table.name};")
sql_content.append("SET FOREIGN_KEY_CHECKS = 1;\n")
# 2. 创建表结构
sql_content.append("-- 2. Create Tables")
for table in Base.metadata.sorted_tables:
create_table_sql = CreateTable(table).compile(compile_engine)
sql_content.append(str(create_table_sql).strip() + ";\n")
# 3. 插入初始数据
sql_content.append("-- 3. Insert Initial Data")
# 用户组
sql_content.append("-- Default User Group")
sql_content.append("INSERT INTO plugin_groups (name, comment) VALUES ('default', 'Default User Group');")
# VIP 配置
sql_content.append("-- VIP Config")
sql_content.append("INSERT INTO vip_config (vip_type, name, price, daily_quota, points_multiplier) VALUES ('vip', 'VIP会员', 30.0, 20, 1.5);")
sql_content.append("INSERT INTO vip_config (vip_type, name, price, daily_quota, points_multiplier) VALUES ('svip', 'SVIP会员', 88.0, -1, 2.0);")
# 签到配置
sql_content.append("-- Check-in Config")
sql_content.append("INSERT INTO checkin_config (consecutive_days, base_points, bonus_points, total_points) VALUES (1, 10, 0, 10);")
sql_content.append("INSERT INTO checkin_config (consecutive_days, base_points, bonus_points, total_points) VALUES (3, 10, 5, 15);")
sql_content.append("INSERT INTO checkin_config (consecutive_days, base_points, bonus_points, total_points) VALUES (7, 10, 20, 30);")
# 功能配置
sql_content.append("-- Feature Config")
sql_content.append("INSERT INTO feature_configs (feature_key, feature_name, points_cost, category) VALUES ('ai_remove_bg', '智能抠图', 10, 'ai');")
# 4. 创建管理员
sql_content.append("-- 4. Create Admin User (admin / password123)")
# 计算密码哈希 (这里直接计算一次固定的,避免每次运行不一样)
# password123 的 bcrypt hash (示例)
# 为了准确,我们还是用 python 算一下
admin_hash = get_password_hash("password123")
# 获取 default group id (假设是 1因为刚刚插入且是第一个)
sql_content.append(f"""
INSERT INTO users (
username, hashed_password, email, is_verified, permissions,
group_id, nickname, level, vip_type, vip_expire,
created_at, points, total_check_in_days, consecutive_check_in, vip_daily_quota
) VALUES (
'admin',
'{admin_hash}',
'admin@example.com',
1,
'admin,vip,svip',
(SELECT id FROM plugin_groups WHERE name = 'default' LIMIT 1),
'Administrator',
999,
'svip',
'2099-12-31 23:59:59',
NOW(),
0, 0, 0, 0
);
""")
# 写入文件
output_file = "init_db.sql"
with open(output_file, "w", encoding="utf-8") as f:
f.write("\n".join(sql_content))
logger.info(f"✅ SQL 文件已生成: {output_file}")
print(f"\nSQL 文件已生成: {os.path.abspath(output_file)}")
print("您可以直接在 phpMyAdmin 中导入此文件来初始化数据库。")
def get_db_url():
"""获取数据库连接 URL"""
# 优先使用 settings 中的配置
# db_url = settings.DATABASE_URL
# 用户指定的远程数据库
# Host: 103.97.201.136
# Port: 3388 (映射到了容器内的 3306)
# User: designer_user
# Pass: DesignerPass123!
db_url = "mysql+pymysql://designer_user:DesignerPass123!@103.97.201.136:3388/designer_db"
# 如果 settings 是默认的 sqlite尝试构建 mysql 连接(用于本地开发时的强制覆盖)
# if db_url.startswith("sqlite"):
# # 默认开发环境 Docker MySQL 配置
# db_url = "mysql+pymysql://designer_user:DesignerPass123!@localhost:3306/designer_db"
# logger.info(f"⚠️ 检测到 SQLite 配置,切换为默认 MySQL 配置: {db_url}")
logger.info(f"🔌 使用数据库配置: {db_url}")
return db_url
def recreate_database(engine, db_name):
"""重建数据库"""
logger.info(f"🗑️ 正在重建数据库: {db_name}...")
# 获取 root 连接(连接到 mysql 系统库或不指定库)
url = make_url(engine.url)
root_url = url.set(database='mysql')
# 使用 AUTOCOMMIT 隔离级别
root_engine = create_engine(root_url, isolation_level="AUTOCOMMIT")
with root_engine.connect() as conn:
conn.execute(text(f"DROP DATABASE IF EXISTS {db_name}"))
conn.execute(text(f"CREATE DATABASE {db_name} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"))
logger.info(f"✅ 数据库 {db_name} 重建完成")
def seed_initial_data(session):
"""填充初始数据"""
logger.info("🌱 正在填充初始数据...")
# 1. 默认用户组
if session.query(PluginGroup).filter(PluginGroup.name == "default").count() == 0:
logger.info(" - 创建默认用户组 'default'")
default_group = PluginGroup(name="default", comment="Default User Group")
session.add(default_group)
# 2. VIP 配置
if session.query(VipConfig).count() == 0:
logger.info(" - 创建 VIP 配置")
session.add(VipConfig(vip_type="vip", name="VIP会员", price=30.0, daily_quota=20, points_multiplier=1.5))
session.add(VipConfig(vip_type="svip", name="SVIP会员", price=88.0, daily_quota=-1, points_multiplier=2.0))
# 3. 签到配置
if session.query(CheckInConfig).count() == 0:
logger.info(" - 创建签到配置")
session.add(CheckInConfig(consecutive_days=1, base_points=10, bonus_points=0, total_points=10))
session.add(CheckInConfig(consecutive_days=3, base_points=10, bonus_points=5, total_points=15))
session.add(CheckInConfig(consecutive_days=7, base_points=10, bonus_points=20, total_points=30))
# 4. 功能配置
if session.query(FeatureConfig).count() == 0:
logger.info(" - 创建功能配置")
session.add(FeatureConfig(feature_key="ai_remove_bg", feature_name="智能抠图", points_cost=10, category="ai"))
session.commit()
logger.info("✅ 初始数据填充完成")
def create_admin_user(session):
"""创建管理员用户"""
username = "admin"
password = "123456"
email = "admin@example.com"
existing = session.query(User).filter(User.username == username).first()
if existing:
logger.info(f" 管理员用户 '{username}' 已存在,跳过创建")
return
logger.info(f"👤 正在创建管理员用户: {username} ...")
# 获取默认组
default_group = session.query(PluginGroup).filter(PluginGroup.name == "default").first()
group_id = default_group.id if default_group else None
new_user = User(
username=username,
hashed_password=get_password_hash(password),
email=email,
is_verified=True,
permissions="admin,vip,svip", # 赋予所有权限
group_id=group_id,
nickname="Administrator",
level=999,
vip_type="svip",
vip_expire=datetime(2099, 12, 31)
)
session.add(new_user)
session.commit()
logger.info(f"✅ 管理员用户创建成功!(User: {username}, Pass: {password})")
def main():
parser = argparse.ArgumentParser(description='Initialize DesignerCEP Database')
parser.add_argument('--generate-sql', action='store_true', help='Generate init_db.sql file only')
args = parser.parse_args()
if args.generate_sql:
generate_sql_file()
return
logger.info("🚀 开始全量数据库初始化流程")
try:
db_url = get_db_url()
engine = create_engine(db_url)
# 1. 重建数据库
# 注意:这会删除现有数据!
db_name = make_url(db_url).database
recreate_database(engine, db_name)
# 重新连接到新创建的数据库
target_engine = create_engine(db_url)
# 2. 创建表结构
logger.info("🏗️ 正在创建表结构...")
Base.metadata.create_all(bind=target_engine)
logger.info("✅ 表结构创建完成")
# 3. 数据填充
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=target_engine)
session = SessionLocal()
try:
seed_initial_data(session)
create_admin_user(session)
finally:
session.close()
logger.info("✨✨✨ 数据库初始化全部完成! ✨✨✨")
except Exception as e:
logger.error(f"❌ 初始化失败: {str(e)}")
sys.exit(1)
if __name__ == "__main__":
main()