一、系统架构设计
HTTPS
有新版本
无更新
客户端
更新服务器
版本检测接口
更新包下载
版本比较
生成差异包
结束
分块加密传输
本地校验
静默安装
进程重启
二、核心代码实现
1. 版本检测模块(VersionChecker.cs)
csharp
public class VersionInfo
{
public string CurrentVersion { get; set; }
public string LatestVersion { get; set; }
public string UpdateUrl { get; set; }
public long FileSize { get; set; }
public string Checksum { get; set; }
}
public async Task<VersionInfo> CheckUpdateAsync()
{
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("User-Agent", "WinUpdater/1.0");
// 获取版本信息
var versionResp = await client.GetAsync("https://update.example.com/api/version");
var versionData = JsonConvert.DeserializeObject<VersionInfo>(await versionResp.Content.ReadAsStringAsync());
// 比较版本
if (new Version(versionData.LatestVersion) > new Version(Assembly.GetExecutingAssembly().GetName().Version))
{
versionData.UpdateUrl = $"https://update.example.com/update_{versionData.LatestVersion}.zip";
return versionData;
}
return null;
}
2. 差分更新模块(DiffUpdate.cs)
csharp
public class DiffUpdate
{
private readonly string _localDir = Path.Combine(Application.StartupPath, "temp");
private readonly string _updateFile = "update.zip";
public async Task DownloadUpdateAsync(string url)
{
using var client = new HttpClient();
client.DownloadProgressChanged += (s, e) => UpdateProgress(e.ProgressPercentage);
var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode();
using var stream = await response.Content.ReadAsStreamAsync();
using var fileStream = new FileStream(Path.Combine(_localDir, _updateFile), FileMode.Create);
await stream.CopyToAsync(fileStream);
}
private void UpdateProgress(int percentage)
{
if (InvokeRequired)
{
Invoke(new Action(() => UpdateProgress(percentage)));
return;
}
progressBar.Value = percentage;
lblStatus.Text = $"{percentage}% ({new FileInfo(Path.Combine(_localDir, _updateFile)).Length / 1024} KB)";
}
}
3. 文件校验与替换(FileHandler.cs)
csharp
public class FileHandler
{
public bool VerifyChecksum(string filePath, string expectedHash)
{
using var sha256 = SHA256.Create();
using var stream = File.OpenRead(filePath);
var hash = BitConverter.ToString(sha256.ComputeHash(stream)).Replace("-", "").ToLower();
return hash == expectedHash;
}
public void ApplyUpdate(string zipPath, string backupDir)
{
if (!Directory.Exists(backupDir)) Directory.CreateDirectory(backupDir);
// 备份旧文件
var files = Directory.GetFiles(Application.StartupPath, "*.*", SearchOption.AllDirectories);
foreach (var file in files)
{
var relativePath = file.Substring(Application.StartupPath.Length + 1);
var backupPath = Path.Combine(backupDir, relativePath);
Directory.CreateDirectory(Path.GetDirectoryName(backupPath));
File.Copy(file, backupPath, true);
}
// 解压更新包
ZipFile.ExtractToDirectory(zipPath, Application.StartupPath, overwrite: true);
// 清理临时文件
Directory.Delete(backupDir, true);
File.Delete(zipPath);
}
}
三、用户界面设计(WinForm)
1. 主界面布局(MainForm.Designer.cs)
csharp
partial class MainForm
{
private System.ComponentModel.IContainer components = null;
private ProgressBar progressBar;
private Label lblStatus;
private Button btnCheckUpdate;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
private void InitializeComponent()
{
this.btnCheckUpdate = new System.Windows.Forms.Button();
this.progressBar = new System.Windows.Forms.ProgressBar();
this.lblStatus = new System.Windows.Forms.Label();
this.btnCheckUpdate.Location = new System.Drawing.Point(20, 20);
this.btnCheckUpdate.Size = new System.Drawing.Size(120, 30);
this.btnCheckUpdate.Text = "检查更新";
this.btnCheckUpdate.Click += new System.EventHandler(this.btnCheckUpdate_Click);
this.progressBar.Location = new System.Drawing.Point(20, 60);
this.progressBar.Size = new System.Drawing.Size(400, 23);
this.lblStatus.Location = new System.Drawing.Point(20, 90);
this.lblStatus.AutoSize = true;
this.ClientSize = new System.Drawing.Size(450, 150);
this.Controls.Add(this.lblStatus);
this.Controls.Add(this.progressBar);
this.Controls.Add(this.btnCheckUpdate);
this.Name = "MainForm";
this.Text = "自动更新客户端";
}
}
四、关键功能实现
1. 静默安装与进程重启
csharp
public void PerformSilentInstall()
{
// 生成批处理脚本
var batPath = Path.Combine(Application.StartupPath, "update.bat");
File.WriteAllText(batPath, $@"
@echo off
taskkill /f /im {Assembly.GetExecutingAssembly().GetName().Name}.exe
timeout /t 2
start "" "" {Path.Combine(Application.StartupPath, "update.exe")}
exit
");
// 执行更新
Process.Start(batPath);
Application.Exit();
}
2. 断点续传实现
csharp
public async Task ResumeDownload(string url, string localPath)
{
using var client = new HttpClient();
var rangeHeader = new RangeHeaderValue(File.Exists(localPath)
? new FileInfo(localPath).Length
: 0, null);
client.DefaultRequestHeaders.Range.Add(rangeHeader);
using var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
using var fileStream = new FileStream(localPath, FileMode.Append);
await response.Content.CopyToAsync(fileStream);
}
五、服务器端配置
1. 更新信息接口(ASP.NET Core)
csharp
[ApiController]
[Route("api/[controller]")]
public class UpdateController : ControllerBase
{
[HttpGet("version")]
public IActionResult GetVersion()
{
var version = new {
current = "1.0.0",
latest = "1.0.1",
url = "https://update.example.com/updates/1.0.1.zip",
checksum = "a1b2c3d4e5f6..."
};
return Ok(version);
}
}
2. 更新包构建工具
csharp
public class UpdateBuilder
{
public void CreateUpdatePackage(string sourceDir, string outputPath)
{
using var zip = new ZipArchive(File.Create(outputPath), ZipArchiveMode.Create);
foreach (var file in Directory.GetFiles(sourceDir, "*.*", SearchOption.AllDirectories))
{
var entry = zip.CreateEntry(Path.GetRelativePath(sourceDir, file));
using var entryStream = entry.Open();
File.Copy(file, entryStream.Name, true);
}
}
}
六、安全增强方案
1. 数字签名验证
csharp
public bool VerifySignature(string filePath)
{
var signer = new SignedCms();
signer.Decode(File.ReadAllBytes(filePath));
signer.VerifySignature(false);
return true;
}
2. 回滚机制
csharp
public void RollbackUpdate(string backupDir)
{
var backupFiles = Directory.GetFiles(backupDir, "*.*", SearchOption.AllDirectories);
foreach (var file in backupFiles)
{
var relativePath = file.Substring(backupDir.Length + 1);
var targetPath = Path.Combine(Application.StartupPath, relativePath);
Directory.CreateDirectory(Path.GetDirectoryName(targetPath));
File.Copy(file, targetPath, true);
}
}
七、部署与测试
1. 安装包配置(MSI)
csharp
<!-- WiX安装配置 -->
<Product Id="*" Name="AutoUpdater" Language="1033" Version="1.0.0">
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLDIR" Name="AutoUpdater">
<Component Id="MainComponent">
<File Id="UpdateExe" Source="AutoUpdater.exe"/>
<File Id="UpdateDll" Source="UpdateLib.dll"/>
</Component>
</Directory>
</Directory>
</Directory>
</Product>
2. 压力测试方案
csharp
// 模拟1000用户并发更新
var tasks = new List<Task>();
for (int i = 0; i < 1000; i++)
{
tasks.Add(Task.Run(async () =>
{
var client = new HttpClient();
await client.GetAsync("https://update.example.com/update.zip");
}));
}
await Task.WhenAll(tasks);
参考代码 基于C#开发的一个Winform自动在线升级程序 www.youwenfan.com/contentcsr/112231.html
八、扩展功能建议
-
增量更新:仅下载差异文件(bsdiff算法)
-
多通道支持:同时支持HTTP/HTTPS/FTP协议
-
灰度发布:按用户组逐步推送更新
-
热更新:不重启进程的DLL替换方案