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:
@@ -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`
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|
||||||
|
|||||||
@@ -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": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user