diff --git a/pyproject.toml b/pyproject.toml index 6706217..ead44cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,5 +48,4 @@ packages = ["src/chongming"] [project.scripts] build = "chongming.scripts.build:build" serve = "chongming:serve" -# gunicorn = "gunicorn chongming.app:app -k uvicorn.workers.UvicornWorker -w 4 -b 0.0.0.0:8000" gunicorn = "chongming:gunicorn_serve" diff --git a/src/chongming/scripts/build.py b/src/chongming/scripts/build.py index b1b2474..3d20eda 100644 --- a/src/chongming/scripts/build.py +++ b/src/chongming/scripts/build.py @@ -1,30 +1,32 @@ -import platform +# build.py - 构建脚本(重构后,逻辑保持不变) -from module_bank import PythonToSQLite -from module_bank.encryption import Encryption +import platform import os -from pathlib import Path import shutil import subprocess import sys import asyncio +from pathlib import Path + +from module_bank import PythonToSQLite +from module_bank.encryption import Encryption from sqlite_vfs.core import SQLiteVFS from sqlite_vfs.folder_packer import FolderPacker - is_windows = platform.system() == "Windows" + +# --------------------------------------------------------------------------- +# 前端构建 +# --------------------------------------------------------------------------- def build_vue_web(): - """打包 Vue Web""" - # 构建 Vue Web 命令 + """构建 Vue 前端并打包为 static.svfs""" dist_dir = "./src/chongming-web/dist" output_db = "./build/static.svfs" fs_name = "frontend" - cmd = [ - "npm", - "run", - "build", - ] + + # 1. 执行 npm run build + cmd = ["npm", "run", "build"] result = subprocess.run( cmd, cwd="./src/chongming-web", @@ -43,19 +45,19 @@ def build_vue_web(): print("Vue Web 打包成功") if result.stdout: print(result.stdout) - - # 检查 dist 目录是否存在 + + # 2. 检查 dist 目录 if not os.path.exists(dist_dir): print(f"错误: dist 目录不存在: {dist_dir}") return - + + # 3. 打包到 svfs vfs = SQLiteVFS(output_db, compress=False) packer = FolderPacker( sqlite_vfs=vfs, exclude_patterns=[], fs_name=fs_name, ) - try: packer.pack_folder(dist_dir) print(f"✅ 打包完成,文件保存为 {output_db}") @@ -63,8 +65,11 @@ def build_vue_web(): vfs.close() +# --------------------------------------------------------------------------- +# PyArmor 混淆 +# --------------------------------------------------------------------------- def run_pyarmor_obfuscate(): - # pyarmor obfuscate --output=obfuscated_script.py original_script.py + """对关键文件执行 PyArmor 混淆""" obfuscate_files = [ ("main.py", "./"), ("server.py", "./"), @@ -88,10 +93,12 @@ def run_pyarmor_obfuscate(): print(f"打包文件: {file}") +# --------------------------------------------------------------------------- +# PyInstaller 打包 +# --------------------------------------------------------------------------- def run_pyinstaller(): - """运行 PyInstaller 打包成可执行文件""" + """使用 PyInstaller 打包成可执行文件""" try: - # 构建 PyInstaller 命令 if is_windows: cmd = [ sys.executable, @@ -107,7 +114,6 @@ def run_pyinstaller(): "--specpath", "./specs", "--clean", - # 隐藏导入 "--hidden-import", "utils", "--hidden-import", @@ -150,7 +156,6 @@ def run_pyinstaller(): "sqlite_vfs.core", "--hidden-import", "jwt", - # 其他选项 "--icon", "../../public/chongming.ico", "main.py", @@ -170,7 +175,6 @@ def run_pyinstaller(): "--specpath", "./specs", "--clean", - # 隐藏导入 "--hidden-import", "utils", "--hidden-import", @@ -213,14 +217,13 @@ def run_pyinstaller(): "sqlite_vfs.core", "--hidden-import", "jwt", - # gunicorn + # gunicorn 相关 "--hidden-import", "gunicorn.glogging", "--hidden-import", "gunicorn.app", "--hidden-import", "gunicorn.app.wsgiapp", - # 其他选项 "--icon", "../../public/chongming.ico", "main.py", @@ -242,11 +245,18 @@ def run_pyinstaller(): print(f"PyInstaller 执行出错: {e}") +# --------------------------------------------------------------------------- +# 数据库初始化 +# --------------------------------------------------------------------------- async def init_database(): - from sqlalchemy.ext.asyncio import create_async_engine + """构建环境下的数据库初始化(创建表、管理员、权限)""" + from sqlalchemy.ext.asyncio import ( + create_async_engine, + AsyncSession, + async_sessionmaker, + ) from sqlmodel import SQLModel, text from sqlalchemy.exc import OperationalError - from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker from ..app.core.config import get_config from ..app.task.dev_init_db import init_permission, dev_init_admin @@ -272,23 +282,27 @@ async def init_database(): await conn.run_sync( lambda conn: SQLModel.metadata.create_all(bind=conn, checkfirst=True) ) - except OperationalError as e: if "already exists" in str(e): raise - # 添加管理员用户 + async with async_session_maker() as session: - # await UserService.create_user(session, "admin", "admin", is_superuser=True) # type: ignore await init_permission(session) await dev_init_admin(session) +# --------------------------------------------------------------------------- +# 构建主流程 +# --------------------------------------------------------------------------- async def main(): - # 打包模块 + # ======================================================== + # 1. 生成密钥并创建 resources 目录 + # ======================================================== key = Encryption.generate_key() resources_path = Path("resources") - if not resources_path.exists(): - resources_path.mkdir() + resources_path.mkdir(exist_ok=True) + + # 2. 打包 Python 模块到 .mbank app_packer = PythonToSQLite("./resources/app.mbank", key) app_packer.pack_directory("src/chongming/app", "app") app_packer.verify_package_structure() @@ -299,72 +313,70 @@ async def main(): plugin_packer.verify_package_structure() plugin_packer.delete_source_code(None) - # 创建 build 目录 - build_path = Path("build") - if build_path.exists() and build_path.is_dir(): + # 3. 准备 build 目录 + if Path("build").exists() and Path("build").is_dir(): shutil.rmtree("build") - os.makedirs("build", exist_ok=True) - # 复制 utils 文件夹到 build 文件夹 - utils_path = Path("utils") - if utils_path.exists(): - target_utils_path = Path("build") / "utils" - shutil.copytree(utils_path, target_utils_path, dirs_exist_ok=True) - print(f"已复制 utils 文件夹到 {target_utils_path}") + # 复制 resources 到 build + shutil.copytree(resources_path, Path("build") / "resources", dirs_exist_ok=True) + print(f"已复制 resources 文件夹到 build/resources") - # 复制 resources 文件夹到 build 文件夹 - resources_path = Path("resources") - if resources_path.exists(): - target_resources_path = Path("build") / "resources" - shutil.copytree(resources_path, target_resources_path, dirs_exist_ok=True) - print(f"已复制 resources 文件夹到 {target_resources_path}") + # 复制 public 中的文件到 build + shutil.copy("public/main.py", "build") + shutil.copy("public/server.py", "build") + shutil.copy("public/config.toml", "build") + # 复制 utils(先复制 public/utils,后续会处理 launch.py 中的 key) + utils_source = Path("public/utils") + if utils_source.exists(): + shutil.copytree(utils_source, Path("build") / "utils", dirs_exist_ok=True) + print(f"已复制 utils 文件夹到 build/utils") print("模块打包完成") - # 复制 main.py 到 build 文件夹 - shutil.copy("public/main.py", "build") - shutil.copy("public/server.py", "build") - # 复制 config.toml 到 build 文件夹 - shutil.copy("public/config.toml", "build") - # 复制 utils 文件夹到 build 文件夹 - shutil.copytree("public/utils", "build/utils") - - with open("./build/utils/launch.py", "r", encoding="utf-8") as f: - context = f.read() - context = context.replace("key = None", f"key = '{key}'") - with open("./build/utils/launch.py", "w", encoding="utf-8") as f: - f.write(context) + # 4. 替换密钥 + launch_file = Path("./build/utils/launch.py") + launch_content = launch_file.read_text(encoding="utf-8") + launch_content = launch_content.replace("key = None", f"key = '{key}'") + launch_file.write_text(launch_content, encoding="utf-8") jwt_key = Encryption.generate_key() - with open("./build/config.toml", "r", encoding="utf-8") as f: - context = f.read() - context = context.replace("your-secret-key-change-in-production", jwt_key) - with open("build/config.toml", "w", encoding="utf-8") as f: - f.write(context) + config_file = Path("./build/config.toml") + config_content = config_file.read_text(encoding="utf-8") + config_content = config_content.replace( + "your-secret-key-change-in-production", jwt_key + ) + config_file.write_text(config_content, encoding="utf-8") - # 混淆 + # 5. 混淆 run_pyarmor_obfuscate() - # 运行 PyInstaller + # 6. PyInstaller 打包 run_pyinstaller() - # 清理临时文件 - shutil.rmtree(r"build/build_temp", ignore_errors=True) - shutil.rmtree(r"build/specs", ignore_errors=True) - shutil.rmtree(r"build/utils", ignore_errors=True) - shutil.rmtree(r"build/pyarmor_runtime_000000", ignore_errors=True) - os.remove(r"build/main.py") - os.remove(r"build/server.py") + # 7. 清理临时文件 + for temp_dir in [ + "build/build_temp", + "build/specs", + "build/utils", + "build/pyarmor_runtime_000000", + ]: + shutil.rmtree(temp_dir, ignore_errors=True) + for temp_file in ["build/main.py", "build/server.py"]: + try: + os.remove(temp_file) + except FileNotFoundError: + pass print("构建完成") - # 初始化数据库 + # 8. 初始化数据库(构建输出目录下的 database.db) await init_database() - # 构建 vue 项目 + # 9. 构建前端并生成 static.svfs build_vue_web() def build(): + """构建入口""" asyncio.run(main())