fix: preserve config across Velopack updates
This commit is contained in:
@@ -40,7 +40,7 @@
|
||||
### 启动与界面
|
||||
|
||||
- `App.xaml`:应用资源、HandyControl 主题和 `MainViewModel` 资源注册;启动页是 `Views/HomeWindow.xaml`。
|
||||
- `App.xaml.cs`:Velopack 初始化、后台/手动更新、更新前后 `config.ini`、`data/` 与 `pre/` 备份恢复、全局异常日志。
|
||||
- `App.xaml.cs`:Velopack 初始化、旧版 `current/config.ini` 更新迁移、后台/手动更新、更新前后 `config.ini`、`data/` 与 `pre/` 备份恢复、全局异常日志。
|
||||
- `Views/HomeWindow.xaml`:单窗口主界面,包含五个迁移 Tab、连接状态、SQL 编辑区和批量操作。
|
||||
- `Views/HomeWindow.xaml.cs`:只处理窗口关闭和手动检查更新等窗口事件。
|
||||
- `Views/ConnectionDialog.*`:源库/目标库连接配置与测试。
|
||||
@@ -65,8 +65,8 @@
|
||||
- `GetTableData`:生成表数据 INSERT SQL;
|
||||
- `ExecuteCreate`:按 `\n/` 分隔 SQL,在目标库逐条执行并检查 `USER_ERRORS`。
|
||||
- `Common/SqlSugarHelper.cs`:维护 `orig`(源库)和 `dest`(目标库)两个 SqlSugar 连接。
|
||||
- `Common/SysConfig.cs`:读取和写入运行目录下的 `config.ini`。
|
||||
- `Common/CommonHelper.cs`:运行目录、`data/`、`data/from/`、`pre/` 路径及文本读写。
|
||||
- `Common/SysConfig.cs`:读取和写入用户持久目录下的 `config.ini`。
|
||||
- `Common/CommonHelper.cs`:运行目录、Velopack 持久目录、`config.ini`、`data/`、`data/from/`、`pre/` 路径及文本读写。
|
||||
|
||||
## 真实数据流
|
||||
|
||||
@@ -94,7 +94,8 @@
|
||||
|
||||
## 配置和运行时文件
|
||||
|
||||
- `config.ini` 保存源库和目标库的 IP/服务名、用户名、密码。当前实现是明文读写,首次运行缺失时创建空配置;发布包不得携带本机配置。
|
||||
- `config.ini` 保存源库和目标库的 IP/服务名、用户名、密码。当前实现是明文读写,首次运行缺失时创建空配置;发布包不得携带本机配置。Velopack 安装态/便携态必须写在与 `Update.exe` 同级的根目录,不能写回每次更新都会整体替换的 `current/`;普通未打包运行时仍写程序目录。
|
||||
- 从旧版本首次更新时,`OnAfterUpdateFastCallback` 会在 Velopack 删除旧 `current/` 临时备份之前,将其中的 `config.ini` 迁移到根目录。不要移除该兼容逻辑,除非已确认所有在用旧版本完成迁移。
|
||||
- 不要在日志、回答、补丁或测试输出中展示真实连接串和密码。
|
||||
- 修改或发布时不要用样例配置覆盖用户现有 `config.ini`。
|
||||
- `data/` 和 `pre/` 是用户可编辑、更新时需要保留的运行数据;不要无故清空、批量改名或删除。
|
||||
@@ -157,7 +158,7 @@ dotnet build .\henlo_migration_wpf10\henlo_migration_wpf10.csproj -c Debug --no-
|
||||
dotnet run --project .\henlo_migration_wpf10\henlo_migration_wpf10.csproj
|
||||
```
|
||||
|
||||
启动会读取或创建运行目录下的配置,并可能尝试初始化更新检查;不适合无界面的 CI 验证。
|
||||
启动会读取或创建用户持久目录下的配置,并可能尝试初始化更新检查;不适合无界面的 CI 验证。
|
||||
|
||||
## 版本与发布
|
||||
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# 更新记录
|
||||
|
||||
## 1.0.18 - 2026-08-26
|
||||
|
||||
### 修复
|
||||
|
||||
- 将 `config.ini` 从 Velopack 每次更新都会整体替换的 `current/` 目录迁移到与 `Update.exe` 同级的持久目录。
|
||||
- 增加 Velopack 更新后快速回调:从更新器尚未清理的旧 `current/` 备份中恢复 `config.ini`,兼容 1.0.16 等旧版本首次升级。
|
||||
- 更新前临时备份和更新后恢复统一读写持久目录中的 `config.ini`,后续升级不再依赖 `current/` 内的配置文件。
|
||||
|
||||
## 1.0.17 - 2026-08-26
|
||||
|
||||
### 新增
|
||||
|
||||
+13
-10
@@ -75,7 +75,7 @@ cd .\henlo_migration_wpf10
|
||||
- `.csproj` 只复制 `pre/*.txt|*.fnc|*.prc` 等程序辅助脚本,不复制本机 `config.ini` 或 `data/from/*.txt`;`pack.bat` 还会在打包前再次删除 `publish/config.ini`,并创建空的 `data/from/` 目录。
|
||||
- `pre/hmig_get_table_bos_31.prc`、`pre/hmig_get_table_bos_field_31.prc` 等运行时辅助过程必须出现在发布输出中。
|
||||
- 新安装的索引列表为空;程序添加第一条对应配置时会在 `data/from/` 创建 `table.txt`、`bos.txt`、`bos_field.txt`、`prc.txt` 或 `data.txt`。
|
||||
- 新安装首次运行时会创建值为空的 `config.ini`,由用户在界面配置;不得把真实或样例账号密码放入发布产物。
|
||||
- 新安装首次运行时会在 Velopack 根目录(与 `Update.exe` 同级)创建值为空的 `config.ini`;普通未打包运行时仍在程序目录创建。不得把真实或样例账号密码放入发布产物。
|
||||
|
||||
## Velopack 产物
|
||||
|
||||
@@ -115,16 +115,18 @@ http://zjhenlo.henlo.net:8878/henlo_migration/auto_update/
|
||||
|
||||
`App.xaml.cs` 当前行为:
|
||||
|
||||
1. UI 启动前调用 `VelopackApp.Build().Run()`。
|
||||
2. 从系统临时目录恢复上次更新前备份的 `config.ini`、`data/`、`pre/`。
|
||||
3. 创建指向更新服务器的 `UpdateManager`;非 Velopack 环境创建失败时禁用自动更新。
|
||||
4. 后台静默检查并下载更新;手动“检查更新”使用相同更新源。
|
||||
5. 用户同意立即重启时,先备份 `config.ini`、`data/`、`pre/`,再调用 `ApplyUpdatesAndRestart()`。
|
||||
6. 新版本首次启动时恢复备份并删除临时备份目录。
|
||||
1. UI 启动前注册 `OnAfterUpdateFastCallback` 并调用 `VelopackApp.Build().Run()`。
|
||||
2. 从旧版更新时,更新器替换 `current/` 后会先调用新程序的快速回调;此时旧 `current/` 仍保存在 `packages/VelopackTemp/tmp_*`,程序从中把旧 `config.ini` 迁移到 Velopack 根目录。
|
||||
3. 从系统临时目录恢复上次更新前备份的 `config.ini`、`data/`、`pre/`。
|
||||
4. 创建指向更新服务器的 `UpdateManager`;非 Velopack 环境创建失败时禁用自动更新。
|
||||
5. 后台静默检查并下载更新;手动“检查更新”使用相同更新源。
|
||||
6. 用户同意立即重启时,先备份持久目录中的 `config.ini` 以及 `current/` 下的 `data/`、`pre/`,再调用 `ApplyUpdatesAndRestart()`。
|
||||
7. 新版本首次启动时恢复备份并删除临时备份目录。
|
||||
|
||||
当前限制和未验证项:
|
||||
|
||||
- 更新备份范围包含 `config.ini`、`data/` 和 `pre/`;恢复失败时会记录到 `error.log`,发版验收仍需验证用户配置未丢失。
|
||||
- `config.ini` 必须位于 Velopack 根目录,不能重新放回会被整体替换的 `current/`。旧版本首次升级依赖 `OnAfterUpdateFastCallback` 在更新器清理旧目录前完成迁移;迁移异常记录到根目录的 `config_migration.log`。
|
||||
- 更新备份范围包含持久目录中的 `config.ini` 以及 `current/` 中的 `data/`、`pre/`;恢复失败时会记录到 `error.log`,发版验收仍需验证用户配置未丢失。
|
||||
- 用户下载完成后选择“不立即重启”时,代码没有明确实现下次启动自动应用已下载更新的流程。
|
||||
- 直接运行普通 `publish/` 中的 EXE 时通常不属于 Velopack 安装态,程序会禁用更新或把检查异常写入 `error.log`。
|
||||
- `--noInst` 生成的是便携模式;便携包的检查、下载、应用和重启更新链路尚未完成实际端到端回归,不能声称自动更新可靠可用。
|
||||
@@ -138,5 +140,6 @@ http://zjhenlo.henlo.net:8878/henlo_migration/auto_update/
|
||||
5. 检查便携 ZIP、full/delta 包、`RELEASES` 和两个 JSON 的版本、大小及时间。
|
||||
6. 检查发布包中的 `pre/` 和必要运行文件,并确认产物中不存在 `config.ini` 及开发机 `data/from/*.txt`。
|
||||
7. 在未安装态测试普通 EXE/便携包启动;若任务涉及自动更新,再在对应 Velopack 运行形态下单独验证更新。
|
||||
8. 只有获得上传授权后才能部署服务器;上传后验证 URL 和包完整性。
|
||||
9. 交付说明必须区分:构建验证、打包验证、便携运行验证、自动更新验证和服务器部署验证。
|
||||
8. 使用旧版便携目录写入不含真实凭据的标记配置,应用新版 full 包,确认标记配置已从旧 `current/config.ini` 迁移为根目录 `config.ini`,且新版再次启动不会覆盖。
|
||||
9. 只有获得上传授权后才能部署服务器;上传后验证 URL 和包完整性。
|
||||
10. 交付说明必须区分:构建验证、打包验证、便携运行验证、自动更新验证和服务器部署验证。
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using Velopack;
|
||||
@@ -23,7 +24,12 @@ namespace henlo_migration
|
||||
try
|
||||
{
|
||||
// Velopack 初始化:必须在所有 UI 之前调用
|
||||
VelopackApp.Build().Run();
|
||||
VelopackApp.Build()
|
||||
.OnAfterUpdateFastCallback(_ => PreserveLegacyConfigAfterUpdate())
|
||||
.Run();
|
||||
|
||||
// 兼容手工覆盖 current 目录、旧便携包等非标准升级方式。
|
||||
MigrateLegacyConfig();
|
||||
|
||||
// 更新后恢复用户数据(如果存在备份)
|
||||
RestoreUserData();
|
||||
@@ -164,7 +170,7 @@ namespace henlo_migration
|
||||
var rootDir = AppDomain.CurrentDomain.BaseDirectory;
|
||||
var dataDir = Path.Combine(rootDir, "data");
|
||||
var preDir = Path.Combine(rootDir, "pre");
|
||||
var configPath = Path.Combine(rootDir, "config.ini");
|
||||
var configPath = Common.CommonHelper.ConfigPath;
|
||||
|
||||
if (!Directory.Exists(dataDir) && !Directory.Exists(preDir) && !File.Exists(configPath)) return;
|
||||
|
||||
@@ -206,7 +212,7 @@ namespace henlo_migration
|
||||
|
||||
var backupConfig = Path.Combine(BackupDir, "config.ini");
|
||||
if (File.Exists(backupConfig))
|
||||
File.Copy(backupConfig, Path.Combine(rootDir, "config.ini"), true);
|
||||
File.Copy(backupConfig, Common.CommonHelper.ConfigPath, true);
|
||||
|
||||
Directory.Delete(BackupDir, true);
|
||||
}
|
||||
@@ -225,6 +231,75 @@ namespace henlo_migration
|
||||
CopyDirectory(dir, Path.Combine(targetDir, Path.GetFileName(dir)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Velopack 已替换 current、但尚未删除旧 current 备份时执行,抢救旧版本的 config.ini。
|
||||
/// </summary>
|
||||
private static void PreserveLegacyConfigAfterUpdate()
|
||||
{
|
||||
try
|
||||
{
|
||||
var locator = VelopackLocator.Current;
|
||||
if (string.IsNullOrWhiteSpace(locator.RootAppDir) ||
|
||||
string.IsNullOrWhiteSpace(locator.AppTempDir))
|
||||
return;
|
||||
|
||||
var targetConfig = Path.Combine(locator.RootAppDir, "config.ini");
|
||||
if (File.Exists(targetConfig)) return;
|
||||
|
||||
var legacyConfig = Directory.EnumerateDirectories(locator.AppTempDir, "tmp_*")
|
||||
.Select(path => Path.Combine(path, "config.ini"))
|
||||
.Where(File.Exists)
|
||||
.OrderByDescending(File.GetLastWriteTimeUtc)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (legacyConfig == null) return;
|
||||
|
||||
Directory.CreateDirectory(locator.RootAppDir);
|
||||
File.Copy(legacyConfig, targetConfig, false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMigrationException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>普通启动时把仍位于 current 的旧配置迁移到 Velopack 持久目录。</summary>
|
||||
private static void MigrateLegacyConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
var targetConfig = Common.CommonHelper.ConfigPath;
|
||||
var legacyConfig = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "config.ini");
|
||||
|
||||
if (!Path.GetFullPath(targetConfig).Equals(
|
||||
Path.GetFullPath(legacyConfig), StringComparison.OrdinalIgnoreCase) &&
|
||||
!File.Exists(targetConfig) &&
|
||||
File.Exists(legacyConfig))
|
||||
{
|
||||
File.Copy(legacyConfig, targetConfig, false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMigrationException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void LogMigrationException(Exception ex)
|
||||
{
|
||||
try
|
||||
{
|
||||
var logRoot = VelopackLocator.IsCurrentSet &&
|
||||
!string.IsNullOrWhiteSpace(VelopackLocator.Current.RootAppDir)
|
||||
? VelopackLocator.Current.RootAppDir
|
||||
: AppDomain.CurrentDomain.BaseDirectory;
|
||||
File.AppendAllText(
|
||||
Path.Combine(logRoot, "config_migration.log"),
|
||||
$"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {ex.Message}{Environment.NewLine}");
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void LogException(string type, Exception ex)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -11,6 +11,33 @@ namespace henlo_migration.Common
|
||||
/// <summary>程序运行根目录</summary>
|
||||
public static string RootPath => AppDomain.CurrentDomain.BaseDirectory;
|
||||
|
||||
/// <summary>
|
||||
/// 用户配置持久目录。Velopack 更新会整体替换 current 目录,因此安装态/便携态使用其父目录;
|
||||
/// 普通 dotnet run 或 publish 运行时仍使用程序目录。
|
||||
/// </summary>
|
||||
public static string PersistentRootPath
|
||||
{
|
||||
get
|
||||
{
|
||||
var rootPath = Path.GetFullPath(RootPath)
|
||||
.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
|
||||
var rootDir = new DirectoryInfo(rootPath);
|
||||
var parentDir = rootDir.Parent;
|
||||
|
||||
if (parentDir != null &&
|
||||
rootDir.Name.Equals("current", StringComparison.OrdinalIgnoreCase) &&
|
||||
File.Exists(Path.Combine(parentDir.FullName, "Update.exe")))
|
||||
{
|
||||
return parentDir.FullName;
|
||||
}
|
||||
|
||||
return rootDir.FullName;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Oracle 连接配置文件路径</summary>
|
||||
public static string ConfigPath => Path.Combine(PersistentRootPath, "config.ini");
|
||||
|
||||
/// <summary>源数据文件夹(存放 table.txt、bos.txt、prc.txt、data.txt 及各表的 .txt 文件)</summary>
|
||||
public static string DataPath
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace henlo_migration.Common
|
||||
{
|
||||
private static IniData _init;
|
||||
|
||||
private static string ConfigPath => System.IO.Path.Combine(CommonHelper.RootPath, "config.ini");
|
||||
private static string ConfigPath => CommonHelper.ConfigPath;
|
||||
static SysConfig()
|
||||
{
|
||||
var parser = new FileIniDataParser();
|
||||
|
||||
@@ -90,7 +90,9 @@ henlo_migration_wpf10/
|
||||
- **源库**:读取数据的数据库
|
||||
- **目标库**:写入数据的数据库
|
||||
|
||||
连接信息保存在运行目录的 `config.ini` 中。发布包不携带该文件,首次运行会创建空配置。
|
||||
连接信息保存在用户配置持久目录的 `config.ini` 中。Velopack 安装态或便携态位于与 `Update.exe` 同级的根目录,普通 `dotnet run`/`publish` 运行时位于程序目录。发布包不携带该文件,首次运行会创建空配置。
|
||||
|
||||
从旧版本更新时,新程序会在 Velopack 删除旧 `current/` 备份前迁移原 `current/config.ini`;后续更新只替换 `current/`,不会覆盖根目录中的配置。
|
||||
|
||||
### 2. 索引文件
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<Nullable>disable</Nullable>
|
||||
<ApplicationIcon>Resources\favicon.ico</ApplicationIcon>
|
||||
<Deterministic>true</Deterministic>
|
||||
<Version>1.0.17</Version>
|
||||
<Version>1.0.18</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user