feat: Add NSIS installer target and persistent SQLite storage

- 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 <noreply@anthropic.com>
This commit is contained in:
egg
2025-12-18 08:13:32 +08:00
parent bc37a5392a
commit 08cffbb74a
4 changed files with 113 additions and 17 deletions

View File

@@ -238,11 +238,14 @@ npm start
### 打包方式 ### 打包方式
```batch ```batch
# Windows 全包打包(MySQL 雲端資料庫,預設 # Windows 全包打包(NSIS 安裝檔,推薦
.\scripts\build-client.bat build --embedded-backend --clean .\scripts\build-client.bat build --embedded-backend --clean
# Windows 全包打包SQLite 本地資料庫,適合離線/防火牆環境) # Windows 全包打包SQLite 本地資料庫,適合離線/防火牆環境)
.\scripts\build-client.bat build --embedded-backend --database-type sqlite --clean .\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` | 啟用內嵌後端模式 | | `--embedded-backend` | 啟用內嵌後端模式 |
| `--database-type TYPE` | 資料庫類型:`mysql`(雲端)或 `sqlite`(本地) | | `--database-type TYPE` | 資料庫類型:`mysql`(雲端)或 `sqlite`(本地) |
| `--target TARGET` | 打包目標:`nsis`(安裝檔,預設)或 `portable`(免安裝) |
| `--clean` | 建置前清理所有暫存檔案 | | `--clean` | 建置前清理所有暫存檔案 |
### 打包目標比較
| 特性 | NSIS 安裝檔(推薦) | Portable 免安裝 |
|------|---------------------|-----------------|
| 安裝方式 | 執行安裝精靈 | 直接執行 |
| 資料持久性 | ✅ 安裝目錄內持久保存 | ⚠️ 臨時資料夾關閉後清空 |
| SQLite 位置 | 安裝目錄/data/ | %APPDATA%\Meeting-Assistant |
| 適用場景 | 正式部署 | 快速測試、展示 |
### config.json 配置 ### config.json 配置
全包模式需要在 `config.json` 中配置資料庫和 API 金鑰: 全包模式需要在 `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` 檔案即可 | | 資料備份 | 使用資料庫工具 | 複製 `.db` 檔案即可 |
### Portable 執行檔解壓縮位置 ### Portable 執行檔說明
Portable exe 執行時會解壓縮到 `%TEMP%\Meeting-Assistant` 資料夾(固定路徑,非隨機資料夾)。 Portable exe 執行時會解壓縮到 `%TEMP%\Meeting-Assistant` 資料夾(固定路徑,非隨機資料夾)。
- **優點**Windows Defender 不會每次都提示警告 - **優點**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 資料庫檔案會在首次啟動時自動建立: SQLite 資料庫檔案會在首次啟動時自動建立:
- **開發環境**`backend/data/meeting.db` - **開發環境**`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` 檔案即可。 **備份方式**:直接複製 `.db` 檔案即可。
> **注意**Portable 模式下SQLite 資料庫自動儲存到 `%APPDATA%` 以確保持久性(不會因關閉程式而清空)。
### Expected tables ### Expected tables
- `meeting_users` - `meeting_users`

View File

@@ -15,6 +15,30 @@ def get_base_dir() -> str:
return os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 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: class Settings:
# Server Configuration # Server Configuration
BACKEND_HOST: str = os.getenv("BACKEND_HOST", "0.0.0.0") 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: def get_sqlite_path(self, base_dir: str | None = None) -> str:
"""Get SQLite database file path, resolving relative paths. """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: Args:
base_dir: Base directory for relative paths. If None, uses get_base_dir() base_dir: Base directory for relative paths. If None, auto-detects.
which supports frozen executables.
""" """
# 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: if base_dir is None:
base_dir = get_base_dir() base_dir = get_base_dir()
if self.SQLITE_PATH: 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, self.SQLITE_PATH)
return os.path.join(base_dir, "data", "meeting.db") return os.path.join(base_dir, "data", "meeting.db")

View File

@@ -46,13 +46,21 @@
"win": { "win": {
"target": [ "target": [
{ {
"target": "portable", "target": "nsis",
"arch": ["x64"] "arch": ["x64"]
} }
], ],
"icon": "assets/icon.ico", "icon": "assets/icon.ico",
"signAndEditExecutable": false "signAndEditExecutable": false
}, },
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"artifactName": "${productName}-${version}-setup.${ext}",
"installerIcon": "assets/icon.ico",
"uninstallerIcon": "assets/icon.ico",
"deleteAppDataOnUninstall": false
},
"mac": { "mac": {
"target": [ "target": [
{ {

View File

@@ -28,6 +28,7 @@ set "EMBEDDED_BACKEND=false"
set "CLEAN_BUILD=false" set "CLEAN_BUILD=false"
set "API_URL=" set "API_URL="
set "DATABASE_TYPE=" set "DATABASE_TYPE="
set "BUILD_TARGET=nsis"
REM 解析參數 REM 解析參數
set "COMMAND=help" 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"=="--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"=="--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"=="--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 echo %RED%[ERROR]%NC% 未知參數: %~1
goto :show_help goto :show_help
@@ -399,7 +401,20 @@ echo %BLUE%[STEP]%NC% 打包 Electron 應用...
cd /d "%CLIENT_DIR%" 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 問題) REM 清理可能損壞的 electron-builder 快取(解決 symlink 問題)
set "EB_CACHE=%LOCALAPPDATA%\electron-builder\Cache\winCodeSign" 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 REM 使用 npm run build 或直接執行 node_modules 中的 electron-builder
if exist "node_modules\.bin\electron-builder.cmd" ( 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 ( ) else (
call npx electron-builder --win call npx electron-builder --win %BUILD_TARGET%
) )
if errorlevel 1 ( if errorlevel 1 (
@@ -457,9 +472,19 @@ dir /b "%BUILD_DIR%"
echo. echo.
echo %GREEN%[OK]%NC% 打包完成! echo %GREEN%[OK]%NC% 打包完成!
echo. echo.
echo Windows 使用說明: if /i "%BUILD_TARGET%"=="nsis" (
echo 1. 找到 build\ 中的 .exe 檔案 echo Windows 使用說明 (NSIS 安裝檔):
echo 2. 直接執行即可,無需安裝 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. echo.
goto :eof goto :eof
@@ -550,12 +575,14 @@ echo --skip-sidecar 跳過 Sidecar 打包
echo --skip-backend 跳過 Backend 打包 (預設) echo --skip-backend 跳過 Backend 打包 (預設)
echo --embedded-backend 打包內嵌後端 (全包部署模式) echo --embedded-backend 打包內嵌後端 (全包部署模式)
echo --database-type TYPE 資料庫類型: mysql (雲端) 或 sqlite (本地) echo --database-type TYPE 資料庫類型: mysql (雲端) 或 sqlite (本地)
echo --target TARGET 打包目標: nsis (安裝檔, 預設) 或 portable (免安裝)
echo --clean 建置前先清理 echo --clean 建置前先清理
echo. echo.
echo 範例: echo 範例:
echo %~nx0 build 完整建置 (前端+Sidecar) echo %~nx0 build 完整建置 (NSIS 安裝檔)
echo %~nx0 build --embedded-backend 全包部署 (含內嵌後端) echo %~nx0 build --embedded-backend 全包部署 (含內嵌後端)
echo %~nx0 build --embedded-backend --database-type sqlite 全包部署 + SQLite 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 build --api-url "http://192.168.1.100:8000/api" 指定遠端後端
echo %~nx0 sidecar 僅打包 Sidecar echo %~nx0 sidecar 僅打包 Sidecar
echo %~nx0 electron --skip-sidecar 僅打包 Electron echo %~nx0 electron --skip-sidecar 僅打包 Electron
@@ -564,6 +591,11 @@ echo 部署模式:
echo 分離部署(預設): 前端連接遠端後端,使用 --api-url 指定後端地址 echo 分離部署(預設): 前端連接遠端後端,使用 --api-url 指定後端地址
echo 全包部署: 使用 --embedded-backend 將後端打包進 exe雙擊即可運行 echo 全包部署: 使用 --embedded-backend 將後端打包進 exe雙擊即可運行
echo. echo.
echo 打包目標:
echo nsis預設: 產生安裝檔,安裝後資料持久保存,推薦正式使用
echo portable: 產生免安裝 exe臨時資料夾會在關閉時清空
echo SQLite 資料庫會自動儲存到 %%APPDATA%%\Meeting-Assistant
echo.
echo 資料庫模式: echo 資料庫模式:
echo MySQL預設: 連接雲端資料庫,需要網路存取 echo MySQL預設: 連接雲端資料庫,需要網路存取
echo SQLite: 本地資料庫,適合離線或防火牆環境,使用 --database-type sqlite echo SQLite: 本地資料庫,適合離線或防火牆環境,使用 --database-type sqlite