From 08cffbb74a72ff9c877914ffdef5b106a6e99b0f Mon Sep 17 00:00:00 2001 From: egg Date: Thu, 18 Dec 2025 08:13:32 +0800 Subject: [PATCH] feat: Add NSIS installer target and persistent SQLite storage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add NSIS installer as default target (--target nsis) - Keep portable option available (--target portable) - Store SQLite database in %APPDATA%\Meeting-Assistant for persistence - Portable temp folder cleanup no longer affects SQLite data - Update build-client.bat with --target parameter support - Update DEPLOYMENT.md with new options and comparisons 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- DEPLOYMENT.md | 30 +++++++++++++++++++++----- backend/app/config.py | 44 ++++++++++++++++++++++++++++++++++---- client/package.json | 10 ++++++++- scripts/build-client.bat | 46 ++++++++++++++++++++++++++++++++++------ 4 files changed, 113 insertions(+), 17 deletions(-) diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index 40e93de..c84819f 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -238,11 +238,14 @@ npm start ### 打包方式 ```batch -# Windows 全包打包(MySQL 雲端資料庫,預設) +# Windows 全包打包(NSIS 安裝檔,推薦) .\scripts\build-client.bat build --embedded-backend --clean # Windows 全包打包(SQLite 本地資料庫,適合離線/防火牆環境) .\scripts\build-client.bat build --embedded-backend --database-type sqlite --clean + +# Windows 全包打包(Portable 免安裝,注意臨時資料夾限制) +.\scripts\build-client.bat build --embedded-backend --target portable --clean ``` **打包參數說明:** @@ -251,8 +254,18 @@ npm start |------|------| | `--embedded-backend` | 啟用內嵌後端模式 | | `--database-type TYPE` | 資料庫類型:`mysql`(雲端)或 `sqlite`(本地) | +| `--target TARGET` | 打包目標:`nsis`(安裝檔,預設)或 `portable`(免安裝) | | `--clean` | 建置前清理所有暫存檔案 | +### 打包目標比較 + +| 特性 | NSIS 安裝檔(推薦) | Portable 免安裝 | +|------|---------------------|-----------------| +| 安裝方式 | 執行安裝精靈 | 直接執行 | +| 資料持久性 | ✅ 安裝目錄內持久保存 | ⚠️ 臨時資料夾關閉後清空 | +| SQLite 位置 | 安裝目錄/data/ | %APPDATA%\Meeting-Assistant | +| 適用場景 | 正式部署 | 快速測試、展示 | + ### config.json 配置 全包模式需要在 `config.json` 中配置資料庫和 API 金鑰: @@ -334,7 +347,9 @@ npm start } ``` -> **注意**:SQLite 模式下,資料庫檔案位於 `%TEMP%\Meeting-Assistant\resources\backend\backend\data\meeting.db` +> **注意**:SQLite 資料庫位置會根據打包目標不同: +> - **NSIS 安裝檔**:安裝目錄內的 `data/meeting.db` +> - **Portable**:`%APPDATA%\Meeting-Assistant\data\meeting.db`(持久保存,不會因關閉程式而清空) ### 配置說明 @@ -359,12 +374,14 @@ npm start | 適用場景 | 企業部署、多人共用 | 離線環境、防火牆限制 | | 資料備份 | 使用資料庫工具 | 複製 `.db` 檔案即可 | -### Portable 執行檔解壓縮位置 +### Portable 執行檔說明 Portable exe 執行時會解壓縮到 `%TEMP%\Meeting-Assistant` 資料夾(固定路徑,非隨機資料夾)。 - **優點**:Windows Defender 不會每次都提示警告 -- **SQLite 資料庫位置**:`%TEMP%\Meeting-Assistant\resources\backend\backend\data\meeting.db` +- **注意**:關閉程式後,臨時資料夾會被清空 +- **SQLite 資料庫位置**:自動儲存到 `%APPDATA%\Meeting-Assistant\data\meeting.db`(不會被清空) +- **建議**:正式部署請使用 NSIS 安裝檔(`--target nsis`,預設) ### 啟動流程 @@ -456,10 +473,13 @@ SHOW TABLES LIKE 'meeting_%'; SQLite 資料庫檔案會在首次啟動時自動建立: - **開發環境**:`backend/data/meeting.db` -- **打包後**:`%TEMP%\Meeting-Assistant\resources\backend\backend\data\meeting.db` +- **NSIS 安裝檔**:安裝目錄內的 `data/meeting.db` +- **Portable**:`%APPDATA%\Meeting-Assistant\data\meeting.db` **備份方式**:直接複製 `.db` 檔案即可。 +> **注意**:Portable 模式下,SQLite 資料庫自動儲存到 `%APPDATA%` 以確保持久性(不會因關閉程式而清空)。 + ### Expected tables - `meeting_users` diff --git a/backend/app/config.py b/backend/app/config.py index 68642d2..ce424a1 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -15,6 +15,30 @@ def get_base_dir() -> str: return os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +def get_app_data_dir() -> str: + """Get persistent app data directory for storing user data. + + This directory persists across application restarts, unlike temp folders + used by portable executables. + + Returns: + Windows: %APPDATA%/Meeting-Assistant + macOS: ~/Library/Application Support/Meeting-Assistant + Linux: ~/.config/meeting-assistant + """ + if sys.platform == "win32": + # Windows: Use APPDATA + base = os.environ.get("APPDATA", os.path.expanduser("~")) + return os.path.join(base, "Meeting-Assistant") + elif sys.platform == "darwin": + # macOS: Use Application Support + return os.path.expanduser("~/Library/Application Support/Meeting-Assistant") + else: + # Linux: Use XDG config or fallback to ~/.config + xdg_config = os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")) + return os.path.join(xdg_config, "meeting-assistant") + + class Settings: # Server Configuration BACKEND_HOST: str = os.getenv("BACKEND_HOST", "0.0.0.0") @@ -95,15 +119,27 @@ class Settings: def get_sqlite_path(self, base_dir: str | None = None) -> str: """Get SQLite database file path, resolving relative paths. + For packaged executables (frozen), uses persistent app data directory + to survive portable exe cleanup. For development, uses relative path. + Args: - base_dir: Base directory for relative paths. If None, uses get_base_dir() - which supports frozen executables. + base_dir: Base directory for relative paths. If None, auto-detects. """ + # If absolute path specified, use it directly + if self.SQLITE_PATH and os.path.isabs(self.SQLITE_PATH): + return self.SQLITE_PATH + + # For frozen executables, use persistent app data directory + # This ensures SQLite data survives portable exe temp cleanup + if getattr(sys, "frozen", False): + app_data = get_app_data_dir() + db_name = os.path.basename(self.SQLITE_PATH) if self.SQLITE_PATH else "meeting.db" + return os.path.join(app_data, "data", db_name) + + # For development, use relative path from base_dir if base_dir is None: base_dir = get_base_dir() if self.SQLITE_PATH: - if os.path.isabs(self.SQLITE_PATH): - return self.SQLITE_PATH return os.path.join(base_dir, self.SQLITE_PATH) return os.path.join(base_dir, "data", "meeting.db") diff --git a/client/package.json b/client/package.json index bb07c1c..957e0ac 100644 --- a/client/package.json +++ b/client/package.json @@ -46,13 +46,21 @@ "win": { "target": [ { - "target": "portable", + "target": "nsis", "arch": ["x64"] } ], "icon": "assets/icon.ico", "signAndEditExecutable": false }, + "nsis": { + "oneClick": false, + "allowToChangeInstallationDirectory": true, + "artifactName": "${productName}-${version}-setup.${ext}", + "installerIcon": "assets/icon.ico", + "uninstallerIcon": "assets/icon.ico", + "deleteAppDataOnUninstall": false + }, "mac": { "target": [ { diff --git a/scripts/build-client.bat b/scripts/build-client.bat index 8dc06f1..e1433b8 100644 --- a/scripts/build-client.bat +++ b/scripts/build-client.bat @@ -28,6 +28,7 @@ set "EMBEDDED_BACKEND=false" set "CLEAN_BUILD=false" set "API_URL=" set "DATABASE_TYPE=" +set "BUILD_TARGET=nsis" REM 解析參數 set "COMMAND=help" @@ -44,6 +45,7 @@ if /i "%~1"=="--embedded-backend" (set "EMBEDDED_BACKEND=true" & set "SKIP_BACKE if /i "%~1"=="--clean" (set "CLEAN_BUILD=true" & shift & goto :parse_args) if /i "%~1"=="--api-url" (set "API_URL=%~2" & shift & shift & goto :parse_args) if /i "%~1"=="--database-type" (set "DATABASE_TYPE=%~2" & shift & shift & goto :parse_args) +if /i "%~1"=="--target" (set "BUILD_TARGET=%~2" & shift & shift & goto :parse_args) echo %RED%[ERROR]%NC% 未知參數: %~1 goto :show_help @@ -399,7 +401,20 @@ echo %BLUE%[STEP]%NC% 打包 Electron 應用... cd /d "%CLIENT_DIR%" -echo %BLUE%[INFO]%NC% 目標平台: Windows (Portable) +REM 驗證 BUILD_TARGET +if /i not "%BUILD_TARGET%"=="nsis" if /i not "%BUILD_TARGET%"=="portable" ( + echo %RED%[ERROR]%NC% 無效的打包目標: %BUILD_TARGET% + echo %BLUE%[INFO]%NC% 有效選項: nsis, portable + exit /b 1 +) + +if /i "%BUILD_TARGET%"=="nsis" ( + echo %BLUE%[INFO]%NC% 目標平台: Windows (NSIS 安裝檔) - 推薦 +) else ( + echo %BLUE%[INFO]%NC% 目標平台: Windows (Portable) + echo %YELLOW%[WARN]%NC% 注意: Portable 模式的臨時資料夾會在關閉時清空 + echo %YELLOW%[WARN]%NC% SQLite 資料庫已自動儲存到 %%APPDATA%%\Meeting-Assistant +) REM 清理可能損壞的 electron-builder 快取(解決 symlink 問題) set "EB_CACHE=%LOCALAPPDATA%\electron-builder\Cache\winCodeSign" @@ -412,9 +427,9 @@ echo %BLUE%[INFO]%NC% 執行 electron-builder... REM 使用 npm run build 或直接執行 node_modules 中的 electron-builder if exist "node_modules\.bin\electron-builder.cmd" ( - call "node_modules\.bin\electron-builder.cmd" --win + call "node_modules\.bin\electron-builder.cmd" --win %BUILD_TARGET% ) else ( - call npx electron-builder --win + call npx electron-builder --win %BUILD_TARGET% ) if errorlevel 1 ( @@ -457,9 +472,19 @@ dir /b "%BUILD_DIR%" echo. echo %GREEN%[OK]%NC% 打包完成! echo. -echo Windows 使用說明: -echo 1. 找到 build\ 中的 .exe 檔案 -echo 2. 直接執行即可,無需安裝 +if /i "%BUILD_TARGET%"=="nsis" ( + echo Windows 使用說明 (NSIS 安裝檔): + echo 1. 找到 build\ 中的 *-setup.exe 檔案 + echo 2. 執行安裝檔,選擇安裝目錄 + echo 3. 安裝後從開始選單或桌面捷徑啟動 + echo 4. 資料會持久保存在安裝目錄中 +) else ( + echo Windows 使用說明 (Portable): + echo 1. 找到 build\ 中的 *-portable.exe 檔案 + echo 2. 直接執行,無需安裝 + echo 3. 注意: 關閉程式後臨時檔案會清空 + echo 4. SQLite 資料庫保存在 %%APPDATA%%\Meeting-Assistant +) echo. goto :eof @@ -550,12 +575,14 @@ echo --skip-sidecar 跳過 Sidecar 打包 echo --skip-backend 跳過 Backend 打包 (預設) echo --embedded-backend 打包內嵌後端 (全包部署模式) echo --database-type TYPE 資料庫類型: mysql (雲端) 或 sqlite (本地) +echo --target TARGET 打包目標: nsis (安裝檔, 預設) 或 portable (免安裝) echo --clean 建置前先清理 echo. echo 範例: -echo %~nx0 build 完整建置 (前端+Sidecar) +echo %~nx0 build 完整建置 (NSIS 安裝檔) echo %~nx0 build --embedded-backend 全包部署 (含內嵌後端) echo %~nx0 build --embedded-backend --database-type sqlite 全包部署 + SQLite +echo %~nx0 build --target portable 打包為免安裝 Portable echo %~nx0 build --api-url "http://192.168.1.100:8000/api" 指定遠端後端 echo %~nx0 sidecar 僅打包 Sidecar echo %~nx0 electron --skip-sidecar 僅打包 Electron @@ -564,6 +591,11 @@ echo 部署模式: echo 分離部署(預設): 前端連接遠端後端,使用 --api-url 指定後端地址 echo 全包部署: 使用 --embedded-backend 將後端打包進 exe,雙擊即可運行 echo. +echo 打包目標: +echo nsis(預設): 產生安裝檔,安裝後資料持久保存,推薦正式使用 +echo portable: 產生免安裝 exe,臨時資料夾會在關閉時清空 +echo SQLite 資料庫會自動儲存到 %%APPDATA%%\Meeting-Assistant +echo. echo 資料庫模式: echo MySQL(預設): 連接雲端資料庫,需要網路存取 echo SQLite: 本地資料庫,適合離線或防火牆環境,使用 --database-type sqlite