适用范围:
server/Admin.NET.Core/Update/AutoVersionUpdate.cs与入口项目server/Admin.NET.Web.Entry/UpdateScripts/版本脚本执行链路
目标读者:后端开发、大语言模型执行代理、测试/发布负责人
编写日期:2026-06-11
任务类型:后端基础设施修复 / 数据库迁移执行可靠性治理
当前 Admin.NET 启动阶段通过 UseAutoVersionUpdate() 自动执行入口项目运行目录下的 UpdateScripts/*.sql。该机制负责把版本 SQL 脚本落到数据库中,项目内 S1/S2/S3/S4/S8 等多处表结构、种子数据、MDP 实体登记均依赖此链路。
现状存在“假成功”风险:
AutoVersionUpdate 中间件结束。version.txt 被写为 {version}^{datetime}^True。本任务要求彻底修复执行与成功判定逻辑,使版本脚本执行链路满足:
version.txt,但不继续依赖它作为唯一事实来源。server/Admin.NET.Web.Core/Startup.cs
Configure() 中调用 app.UseAutoVersionUpdate();server/Admin.NET.Core/Update/AutoVersionUpdate.cs
现有逻辑大致为:
SnowId:WorkerId == 1 的主节点执行。ReflectionHelper.GetEntryAssemblyVersion().ToString(3)。version.txt。version.txt 不存在或格式不对,视为历史版本为空。UpdateScripts/*.sql 读取脚本。dbContext.Ado.BeginTran();dbContext.Ado.ExecuteCommand(sql);dbContext.Ado.CommitTran();version.txt = {sqlVersion}^{time}^True。当前逻辑中,当 version.txt 不存在时:
if (historyVersion == string.Empty)
{
LogHelper.Handle("历史版本为空,默认为最新版本,不执行脚本");
SetEntryAssemblyCurrentVersion(currentVersion, true);
return app;
}
这会导致新环境首次启动直接标记当前版本已执行,但 UpdateScripts 里的历史脚本全部未跑。
当前逻辑:
if (scriptSqlVersions.All(s => s.Version.CompareTo(currentVersion) < 0))
{
LogHelper.Handle("不存在当前版本的脚本,只保存当前版本信息,不执行脚本");
SetEntryAssemblyCurrentVersion(currentVersion, false);
return app;
}
如果入口项目版本为 1.0.161,而 UpdateScripts 最新是 1.0.158,即使历史版本是 1.0.154,1.0.155 到 1.0.158 也可能被整体跳过。
ExecuteCommand 是否抛异常没有验证:
当前使用 string.CompareTo。这对语义版本不安全,例如:
1.0.9 与 1.0.101.0.158 与 1.0.99应改为 System.Version 或等价 SemVer 比较。
version.txt 是单点状态,且不在数据库内version.txt 只在本机运行目录中,无法反映:
MySQL ALTER TABLE / CREATE TABLE 等 DDL 通常会隐式提交,不能依赖 BeginTran/CommitTran 保证整文件原子性。
version.txt 不存在时,不得跳过历史脚本。version.txt=True。建议表名:sys_db_migration_log
DDL 由代码启动时幂等创建,不依赖 UpdateScripts 自己创建。
CREATE TABLE IF NOT EXISTS sys_db_migration_log (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
version VARCHAR(50) NOT NULL COMMENT '脚本版本',
file_name VARCHAR(255) NOT NULL COMMENT '脚本文件名',
file_hash VARCHAR(128) NOT NULL COMMENT '脚本 SHA256',
status VARCHAR(20) NOT NULL COMMENT 'Running/Success/Failed/Skipped',
started_at DATETIME(3) NOT NULL COMMENT '开始时间',
finished_at DATETIME(3) NULL COMMENT '结束时间',
elapsed_ms BIGINT NULL COMMENT '耗时毫秒',
statement_count INT NOT NULL DEFAULT 0 COMMENT '执行语句数',
affected_rows BIGINT NULL COMMENT '影响行数合计',
verify_status VARCHAR(20) NULL COMMENT '校验状态:NotConfigured/Success/Failed',
error_message LONGTEXT NULL COMMENT '错误信息',
database_name VARCHAR(128) NULL COMMENT '执行数据库',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (id),
UNIQUE KEY uk_sys_db_migration_log_version (version),
KEY idx_sys_db_migration_log_status (status),
KEY idx_sys_db_migration_log_started_at (started_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据库版本脚本执行记录';
说明:
version 唯一,保持每个版本脚本只成功一次。Failed,允许重试并覆盖状态或新增 retry 字段。为简化,建议更新同一行。优先级:
sys_db_migration_log 中 status='Success' 的版本记录。version.txt,仅作为迁移表为空时的旧状态参考。不得再只依赖 version.txt 判断脚本是否执行。
输入:
currentVersion:入口程序集版本。scriptVersions:UpdateScripts/*.sql 中所有版本脚本。successVersions:迁移记录表中成功版本。historyVersionFromTxt:旧 version.txt 中记录的版本。应执行脚本集合:
<= currentVersion。Success 记录。version.txt 存在且 isRunScript=True,可将 <= historyVersionFromTxt 的脚本视为 legacy 已成功,但建议只作为兼容策略,并打印警告。currentVersion 同名的 SQL 文件。伪代码:
var current = Version.Parse(currentVersion);
var scripts = GetScriptSqlVersions()
.Where(x => Version.Parse(x.Version) <= current)
.OrderBy(x => Version.Parse(x.Version))
.ToList();
foreach (var script in scripts)
{
if (MigrationAlreadySucceeded(script))
continue;
ExecuteOneScript(script);
}
必须新增统一比较方法:
private static Version ParseVersion(string value)
{
if (!Version.TryParse(value, out var version))
throw new InvalidOperationException($"非法版本号:{value}");
return version;
}
若项目后续出现 1.0.158-alpha,再引入 SemVer 库;当前脚本命名均为 x.y.z,System.Version 足够。
保留整文件执行能力,但必须增强结果判断与日志。
var affected = db.Ado.ExecuteCommand(sql);
注意:这仍无法精确定位多语句中的哪条失败,但能先消除“无记录、无校验”的问题。
实现 SQL 脚本分割器,按语句执行:
-- 单行注释。# 单行注释。/* ... */ 块注释。SET / PREPARE / EXECUTE / DEALLOCATE / CREATE / ALTER / INSERT / UPDATE。DELIMITER 与存储过程;若发现脚本含 DELIMITER,应报错并提示手工执行或扩展执行器。执行策略:
Failed,不继续后续脚本。脚本成功必须满足:
Success。version.txt 只在上述三项成功后更新。
建议支持两种方式,至少实现一种。
同版本脚本旁新增可选文件:
UpdateScripts/1.0.158.sql
UpdateScripts/1.0.158.verify.sql
*.verify.sql 可包含一条或多条 SELECT,每条必须返回第一行第一列为 truthy:
1true示例:
SELECT COUNT(*) > 0
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'dwd_po_trans';
在 SQL 文件末尾约定:
-- VERIFY:
-- SELECT COUNT(*) > 0 FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='xxx';
实现复杂度高于方式 A。建议本轮优先实现方式 A。
启动日志至少包含:
SELECT DATABASE()。失败时日志必须包含:
推荐:版本脚本失败应阻止应用继续启动。
理由:
实现:
HandleSqlScript 失败后抛出异常。UseAutoVersionUpdate() 不吞异常。如果项目负责人要求不中断启动,可加配置:
"AutoVersionUpdate": {
"StopApplicationOnFailure": true
}
默认必须为 true。
阅读并理解:
server/Admin.NET.Core/Update/AutoVersionUpdate.csserver/Admin.NET.Web.Core/Startup.csserver/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csprojserver/Admin.NET.Web.Entry/UpdateScripts/*.sql不要改动业务插件代码。
在 AutoVersionUpdate.cs 中新增私有模型:
private sealed record MigrationScript(
string Version,
Version ParsedVersion,
string FilePath,
string FileName,
string Hash);
private sealed class MigrationLogRow
{
public long Id { get; set; }
public string Version { get; set; } = string.Empty;
public string FileHash { get; set; } = string.Empty;
public string Status { get; set; } = string.Empty;
}
也可独立文件,但为减少影响范围,建议本轮集中在 AutoVersionUpdate.cs。
在执行脚本前调用:
EnsureMigrationLogTable(dbContext);
该方法必须执行 CREATE TABLE IF NOT EXISTS sys_db_migration_log (...)。
废弃:
string.CompareTo(...)
改为:
Version.Parse(...)
修复以下逻辑:
version.txt 为空不再直接返回。scriptVersion <= currentVersion && !success 的脚本。使用 SHA256:
private static string ComputeSha256(string filePath)
{
using var sha = SHA256.Create();
using var stream = File.OpenRead(filePath);
return Convert.ToHexString(sha.ComputeHash(stream));
}
需要添加:
using System.Security.Cryptography;
执行脚本前 upsert:
Running。Failed:更新为 Running,清空错误。Success 且 hash 相同:跳过。Success 且 hash 不同:抛异常,阻止启动。优先实现分语句执行。若时间不足,可第一版保留整文件执行,但必须:
推荐接口:
private static ScriptExecuteResult ExecuteSqlScript(ISqlSugarClient db, string sql)
返回:
private sealed record ScriptExecuteResult(int StatementCount, long AffectedRows);
寻找同目录:
var verifyPath = Path.ChangeExtension(script.FilePath, ".verify.sql");
注意 Path.ChangeExtension("1.0.158.sql", ".verify.sql") 会得到 1.0.158.verify.sql。
若存在,逐条执行 SELECT 校验。
校验失败:
verify_status='Failed';version.txt=True。若不存在:
verify_status='NotConfigured'。成功后更新:
status='Success'finished_atelapsed_msstatement_countaffected_rowsverify_statuserror_message=NULLdatabase_name=SELECT DATABASE()version.txt只有当本轮所有应执行脚本成功后,写:
{currentVersion}^{now}^{True}
或者在每个脚本成功后写脚本版本也可,但推荐最终写当前程序集版本,数据库真实状态以 sys_db_migration_log 为准。
任一脚本失败:
Failed。version.txt=True。version.txt如果迁移表为空但 version.txt 存在:
version.txt。isRunScript=True,可将 <= historyVersion 的脚本视为 legacy 已成功,但不要自动批量插入 Success,避免伪造历史。日志提示:
检测到 sys_db_migration_log 为空但 version.txt 存在,将 version.txt 作为 legacy 跳过边界:{historyVersion}
更严格方案:
version.txt,全部重新跑幂等脚本。本任务建议采用兼容边界方案。
至少覆盖:
1.0.9 < 1.0.101.0.158 < 1.0.1611.0.161,脚本最新 1.0.158,历史成功到 1.0.154:
1.0.155、1.0.157、1.0.158 等未成功脚本。version.txt 不存在:
version.txt。sys_db_migration_log。放一个测试脚本:
CREATE TABLE IF NOT EXISTS auto_version_update_test (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
code VARCHAR(50) NOT NULL
);
INSERT INTO auto_version_update_test(code)
SELECT 'OK'
WHERE NOT EXISTS (SELECT 1 FROM auto_version_update_test WHERE code='OK');
启动后验证:
SELECT COUNT(*) FROM auto_version_update_test WHERE code='OK';
SELECT version, status FROM sys_db_migration_log ORDER BY version;
添加一个故意失败脚本:
ALTER TABLE not_exists_table ADD COLUMN x INT;
预期:
sys_db_migration_log.status='Failed'。version.txt 不写 True 到该版本。脚本成功但 .verify.sql 返回 false:
SELECT 0;
预期同失败。
入口项目版本保持 1.0.161,脚本最新 1.0.158。
构造历史成功到 1.0.154。
预期:
1.0.155 到 1.0.158 中未成功脚本会执行。执行后检查:
SELECT version, file_name, status, verify_status, started_at, finished_at, elapsed_ms
FROM sys_db_migration_log
ORDER BY version;
针对关键版本脚本补充业务校验,例如:
-- 表是否存在
SELECT COUNT(*)
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = '目标表名';
-- 字段是否存在
SELECT COUNT(*)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = '目标表名'
AND COLUMN_NAME = '目标字段';
-- 种子数据是否存在
SELECT COUNT(*)
FROM 目标表
WHERE 业务唯一条件;
version.txt 的新环境启动时,不再跳过历史脚本。sys_db_migration_log 有每个脚本的状态记录。.verify.sql 失败时,脚本不标成功。dotnet build server/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj 通过。UpdateScripts 不因新执行器误拆分而报错。version.txt 兼容读取。sys_db_migration_log 时,要明确如何处理已有环境:
version.txt 作为 legacy 边界;DELIMITER / 存储过程脚本若存在,普通分号拆分器会失败。当前脚本初查主要为普通 DDL/DML 与 PREPARE/EXECUTE,但执行前仍需扫描。version.txt 改成 True 掩盖问题,应修复脚本或校验。server/Admin.NET.Core/Update/AutoVersionUpdate.cs
server/Admin.NET.Web.Entry/UpdateScripts/*.verify.sql
请严格按以下顺序执行:
AutoVersionUpdate.cs 当前实现。sys_db_migration_log 幂等建表逻辑。version.txt 不存在时跳过脚本的逻辑。System.Version 比较版本。.verify.sql 校验。执行完成后输出:
.verify.sql。UpdateScripts 补充 .verify.sql。version.txt 作为发布验收依据。发布验收模板增加:
SELECT version, status, verify_status, finished_at
FROM sys_db_migration_log
ORDER BY version DESC
LIMIT 20;
对 DDL 类脚本要求幂等。
对数据种子脚本要求唯一键或 INSERT ... ON DUPLICATE KEY UPDATE。
对非幂等脚本必须在脚本头注明风险与回滚方式。
适用范围:
server/Admin.NET.Core/Update/AutoVersionUpdate.cs与入口项目server/Admin.NET.Web.Entry/UpdateScripts/版本脚本执行链路
目标读者:后端开发、大语言模型执行代理、测试/发布负责人
编写日期:2026-06-11
任务类型:后端基础设施修复 / 数据库迁移执行可靠性治理
当前 Admin.NET 启动阶段通过 UseAutoVersionUpdate() 自动执行入口项目运行目录下的 UpdateScripts/*.sql。该机制负责把版本 SQL 脚本落到数据库中,项目内 S1/S2/S3/S4/S8 等多处表结构、种子数据、MDP 实体登记均依赖此链路。
现状存在“假成功”风险:
AutoVersionUpdate 中间件结束。version.txt 被写为 {version}^{datetime}^True。本任务要求彻底修复执行与成功判定逻辑,使版本脚本执行链路满足:
version.txt,但不继续依赖它作为唯一事实来源。server/Admin.NET.Web.Core/Startup.cs
Configure() 中调用 app.UseAutoVersionUpdate();server/Admin.NET.Core/Update/AutoVersionUpdate.cs
现有逻辑大致为:
SnowId:WorkerId == 1 的主节点执行。ReflectionHelper.GetEntryAssemblyVersion().ToString(3)。version.txt。version.txt 不存在或格式不对,视为历史版本为空。UpdateScripts/*.sql 读取脚本。dbContext.Ado.BeginTran();dbContext.Ado.ExecuteCommand(sql);dbContext.Ado.CommitTran();version.txt = {sqlVersion}^{time}^True。当前逻辑中,当 version.txt 不存在时:
if (historyVersion == string.Empty)
{
LogHelper.Handle("历史版本为空,默认为最新版本,不执行脚本");
SetEntryAssemblyCurrentVersion(currentVersion, true);
return app;
}
这会导致新环境首次启动直接标记当前版本已执行,但 UpdateScripts 里的历史脚本全部未跑。
当前逻辑:
if (scriptSqlVersions.All(s => s.Version.CompareTo(currentVersion) < 0))
{
LogHelper.Handle("不存在当前版本的脚本,只保存当前版本信息,不执行脚本");
SetEntryAssemblyCurrentVersion(currentVersion, false);
return app;
}
如果入口项目版本为 1.0.161,而 UpdateScripts 最新是 1.0.158,即使历史版本是 1.0.154,1.0.155 到 1.0.158 也可能被整体跳过。
ExecuteCommand 是否抛异常没有验证:
当前使用 string.CompareTo。这对语义版本不安全,例如:
1.0.9 与 1.0.101.0.158 与 1.0.99应改为 System.Version 或等价 SemVer 比较。
version.txt 是单点状态,且不在数据库内version.txt 只在本机运行目录中,无法反映:
MySQL ALTER TABLE / CREATE TABLE 等 DDL 通常会隐式提交,不能依赖 BeginTran/CommitTran 保证整文件原子性。
version.txt 不存在时,不得跳过历史脚本。version.txt=True。建议表名:sys_db_migration_log
DDL 由代码启动时幂等创建,不依赖 UpdateScripts 自己创建。
CREATE TABLE IF NOT EXISTS sys_db_migration_log (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
version VARCHAR(50) NOT NULL COMMENT '脚本版本',
file_name VARCHAR(255) NOT NULL COMMENT '脚本文件名',
file_hash VARCHAR(128) NOT NULL COMMENT '脚本 SHA256',
status VARCHAR(20) NOT NULL COMMENT 'Running/Success/Failed/Skipped',
started_at DATETIME(3) NOT NULL COMMENT '开始时间',
finished_at DATETIME(3) NULL COMMENT '结束时间',
elapsed_ms BIGINT NULL COMMENT '耗时毫秒',
statement_count INT NOT NULL DEFAULT 0 COMMENT '执行语句数',
affected_rows BIGINT NULL COMMENT '影响行数合计',
verify_status VARCHAR(20) NULL COMMENT '校验状态:NotConfigured/Success/Failed',
error_message LONGTEXT NULL COMMENT '错误信息',
database_name VARCHAR(128) NULL COMMENT '执行数据库',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (id),
UNIQUE KEY uk_sys_db_migration_log_version (version),
KEY idx_sys_db_migration_log_status (status),
KEY idx_sys_db_migration_log_started_at (started_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据库版本脚本执行记录';
说明:
version 唯一,保持每个版本脚本只成功一次。Failed,允许重试并覆盖状态或新增 retry 字段。为简化,建议更新同一行。优先级:
sys_db_migration_log 中 status='Success' 的版本记录。version.txt,仅作为迁移表为空时的旧状态参考。不得再只依赖 version.txt 判断脚本是否执行。
输入:
currentVersion:入口程序集版本。scriptVersions:UpdateScripts/*.sql 中所有版本脚本。successVersions:迁移记录表中成功版本。historyVersionFromTxt:旧 version.txt 中记录的版本。应执行脚本集合:
<= currentVersion。Success 记录。version.txt 存在且 isRunScript=True,可将 <= historyVersionFromTxt 的脚本视为 legacy 已成功,但建议只作为兼容策略,并打印警告。currentVersion 同名的 SQL 文件。伪代码:
var current = Version.Parse(currentVersion);
var scripts = GetScriptSqlVersions()
.Where(x => Version.Parse(x.Version) <= current)
.OrderBy(x => Version.Parse(x.Version))
.ToList();
foreach (var script in scripts)
{
if (MigrationAlreadySucceeded(script))
continue;
ExecuteOneScript(script);
}
必须新增统一比较方法:
private static Version ParseVersion(string value)
{
if (!Version.TryParse(value, out var version))
throw new InvalidOperationException($"非法版本号:{value}");
return version;
}
若项目后续出现 1.0.158-alpha,再引入 SemVer 库;当前脚本命名均为 x.y.z,System.Version 足够。
保留整文件执行能力,但必须增强结果判断与日志。
var affected = db.Ado.ExecuteCommand(sql);
注意:这仍无法精确定位多语句中的哪条失败,但能先消除“无记录、无校验”的问题。
实现 SQL 脚本分割器,按语句执行:
-- 单行注释。# 单行注释。/* ... */ 块注释。SET / PREPARE / EXECUTE / DEALLOCATE / CREATE / ALTER / INSERT / UPDATE。DELIMITER 与存储过程;若发现脚本含 DELIMITER,应报错并提示手工执行或扩展执行器。执行策略:
Failed,不继续后续脚本。脚本成功必须满足:
Success。version.txt 只在上述三项成功后更新。
建议支持两种方式,至少实现一种。
同版本脚本旁新增可选文件:
UpdateScripts/1.0.158.sql
UpdateScripts/1.0.158.verify.sql
*.verify.sql 可包含一条或多条 SELECT,每条必须返回第一行第一列为 truthy:
1true示例:
SELECT COUNT(*) > 0
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'dwd_po_trans';
在 SQL 文件末尾约定:
-- VERIFY:
-- SELECT COUNT(*) > 0 FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='xxx';
实现复杂度高于方式 A。建议本轮优先实现方式 A。
启动日志至少包含:
SELECT DATABASE()。失败时日志必须包含:
推荐:版本脚本失败应阻止应用继续启动。
理由:
实现:
HandleSqlScript 失败后抛出异常。UseAutoVersionUpdate() 不吞异常。如果项目负责人要求不中断启动,可加配置:
"AutoVersionUpdate": {
"StopApplicationOnFailure": true
}
默认必须为 true。
阅读并理解:
server/Admin.NET.Core/Update/AutoVersionUpdate.csserver/Admin.NET.Web.Core/Startup.csserver/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csprojserver/Admin.NET.Web.Entry/UpdateScripts/*.sql不要改动业务插件代码。
在 AutoVersionUpdate.cs 中新增私有模型:
private sealed record MigrationScript(
string Version,
Version ParsedVersion,
string FilePath,
string FileName,
string Hash);
private sealed class MigrationLogRow
{
public long Id { get; set; }
public string Version { get; set; } = string.Empty;
public string FileHash { get; set; } = string.Empty;
public string Status { get; set; } = string.Empty;
}
也可独立文件,但为减少影响范围,建议本轮集中在 AutoVersionUpdate.cs。
在执行脚本前调用:
EnsureMigrationLogTable(dbContext);
该方法必须执行 CREATE TABLE IF NOT EXISTS sys_db_migration_log (...)。
废弃:
string.CompareTo(...)
改为:
Version.Parse(...)
修复以下逻辑:
version.txt 为空不再直接返回。scriptVersion <= currentVersion && !success 的脚本。使用 SHA256:
private static string ComputeSha256(string filePath)
{
using var sha = SHA256.Create();
using var stream = File.OpenRead(filePath);
return Convert.ToHexString(sha.ComputeHash(stream));
}
需要添加:
using System.Security.Cryptography;
执行脚本前 upsert:
Running。Failed:更新为 Running,清空错误。Success 且 hash 相同:跳过。Success 且 hash 不同:抛异常,阻止启动。优先实现分语句执行。若时间不足,可第一版保留整文件执行,但必须:
推荐接口:
private static ScriptExecuteResult ExecuteSqlScript(ISqlSugarClient db, string sql)
返回:
private sealed record ScriptExecuteResult(int StatementCount, long AffectedRows);
寻找同目录:
var verifyPath = Path.ChangeExtension(script.FilePath, ".verify.sql");
注意 Path.ChangeExtension("1.0.158.sql", ".verify.sql") 会得到 1.0.158.verify.sql。
若存在,逐条执行 SELECT 校验。
校验失败:
verify_status='Failed';version.txt=True。若不存在:
verify_status='NotConfigured'。成功后更新:
status='Success'finished_atelapsed_msstatement_countaffected_rowsverify_statuserror_message=NULLdatabase_name=SELECT DATABASE()version.txt只有当本轮所有应执行脚本成功后,写:
{currentVersion}^{now}^{True}
或者在每个脚本成功后写脚本版本也可,但推荐最终写当前程序集版本,数据库真实状态以 sys_db_migration_log 为准。
任一脚本失败:
Failed。version.txt=True。version.txt如果迁移表为空但 version.txt 存在:
version.txt。isRunScript=True,可将 <= historyVersion 的脚本视为 legacy 成功,但不要自动批量插入 Success,避免伪造历史。日志提示:
检测到 sys_db_migration_log 为空但 version.txt 存在,将 version.txt 作为 legacy 跳过边界:{historyVersion}
更严格方案:
version.txt,全部重新跑幂等脚本。本任务建议采用兼容边界方案。
至少覆盖:
1.0.9 < 1.0.101.0.158 < 1.0.1611.0.161,脚本最新 1.0.158,历史成功到 1.0.154:
1.0.155、1.0.157、1.0.158 等未成功脚本。version.txt 不存在:
version.txt。sys_db_migration_log。放一个测试脚本:
CREATE TABLE IF NOT EXISTS auto_version_update_test (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
code VARCHAR(50) NOT NULL
);
INSERT INTO auto_version_update_test(code)
SELECT 'OK'
WHERE NOT EXISTS (SELECT 1 FROM auto_version_update_test WHERE code='OK');
启动后验证:
SELECT COUNT(*) FROM auto_version_update_test WHERE code='OK';
SELECT version, status FROM sys_db_migration_log ORDER BY version;
添加一个故意失败脚本:
ALTER TABLE not_exists_table ADD COLUMN x INT;
预期:
sys_db_migration_log.status='Failed'。version.txt 不写 True 到该版本。脚本成功但 .verify.sql 返回 false:
SELECT 0;
预期同失败。
入口项目版本保持 1.0.161,脚本最新 1.0.158。
构造历史成功到 1.0.154。
预期:
1.0.155 到 1.0.158 中未成功脚本会执行。执行后检查:
SELECT version, file_name, status, verify_status, started_at, finished_at, elapsed_ms
FROM sys_db_migration_log
ORDER BY version;
针对关键版本脚本补充业务校验,例如:
-- 表是否存在
SELECT COUNT(*)
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = '目标表名';
-- 字段是否存在
SELECT COUNT(*)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = '目标表名'
AND COLUMN_NAME = '目标字段';
-- 种子数据是否存在
SELECT COUNT(*)
FROM 目标表
WHERE 业务唯一条件;
version.txt 的新环境启动时,不再跳过历史脚本。sys_db_migration_log 有每个脚本的状态记录。.verify.sql 失败时,脚本不标成功。dotnet build server/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj 通过。UpdateScripts 不因新执行器误拆分而报错。version.txt 兼容读取。sys_db_migration_log 时,要明确如何处理已有环境:
version.txt 作为 legacy 边界;DELIMITER / 存储过程脚本若存在,普通分号拆分器会失败。当前脚本初查主要为普通 DDL/DML 与 PREPARE/EXECUTE,但执行前仍需扫描。version.txt 改成 True 掩盖问题,应修复脚本或校验。server/Admin.NET.Core/Update/AutoVersionUpdate.cs
server/Admin.NET.Web.Entry/UpdateScripts/*.verify.sql
请严格按以下顺序执行:
AutoVersionUpdate.cs 当前实现。sys_db_migration_log 幂等建表逻辑。version.txt 不存在时跳过脚本的逻辑。System.Version 比较版本。.verify.sql 校验。执行完成后输出:
.verify.sql。UpdateScripts 补充 .verify.sql。version.txt 作为发布验收依据。发布验收模板增加:
SELECT version, status, verify_status, finished_at
FROM sys_db_migration_log
ORDER BY version DESC
LIMIT 20;
对 DDL 类脚本要求幂等。
对数据种子脚本要求唯一键或 INSERT ... ON DUPLICATE KEY UPDATE。
对非幂等脚本必须在脚本头注明风险与回滚方式。