自動更新介紹mysql
咱們作了程序,難免會有版本升級,這就須要程序有自動版本升級的功能。應用程序自動更新是由客戶端應用程序自身負責從一個已知服務器下載並安裝更新,用戶惟一須要進行干預的是決定是否願意如今或之後安裝新的更新。git
客戶端程序要完成自動更新必需要作三件事情:檢查是否有更新;當發現有更新時,開始下載更新;當下載完成時,執行更新操做;分別分析一下這三個步驟: github
一、檢查更新web
客戶端要正確檢查是否有更新須要三個必要過程:sql
(1)到哪裏去更新。即目標服務器的URI(URL或具體目錄)數據庫
(2)什麼時候去檢查。即更新的頻率,每隔多長時間檢查一次。安全
(3)經過什麼方式檢查。通信協議,如HTTP、FTP、FILE等。服務器
(4)如何檢查。如在後臺運行,且開啓獨立線程。app
二、下載更新框架
在普通用戶眼裏,下載是一件再普通不過的事情了,但在開發者眼裏咱們須要考慮更多。我想須要考慮的以下:
(1)到哪裏去下載。即目標Server的URI。
(2)經過什麼方式下載。HTTP、FTP、FILE等,且斷點續傳。
(3)如何下載。在後臺開啓獨立線程以不影響主線程工做。
(4)應對異常。如中斷鏈接後自動從新鏈接,屢次失敗後拋棄等。
三、實現更新
實現更新不能簡單地認爲是一個文件的覆蓋問題,緣由很簡單:一個正在被使用的文件是沒法被覆蓋的。也就是說程序本身是沒法更新本身的。這樣看來,實現更新有兩種方案:
(1)在主程序以外,另開啓一個獨立進程來負責更新主程序。但前提是在更新前必須強制用戶退出主程序,不少現有產品就是這樣作的,如QQ。
(2)主程序根據下載的文件,生成一個比目前存在版本新的應用程序版本,用戶在下次從新打開應用程序時會自動使用新版本,同時原始應用程序的拷貝就能夠被移除了。
2、可用框架
在.NET Framework2.0之後,微軟提供了採用內置的ClickOnce部署方式來實現系統更新,配置比較麻煩應用起來也不是很方便。.NET也有許多流行的開源自動更新組件以下:
序號 名稱 地址
1 AutoUpdater.NET https://autoupdaterdotnet.codeplex.com/
2 wyUpdate http://wyday.com/wyupdate/
3 Updater http://www.codeproject.com/Articles/9566/Updater
4 NetSparkle http://netsparkle.codeplex.com/
5 NAppUpdate https://github.com/synhershko/NAppUpdate
6 AutoUpdater https://autoupdater.codeplex.com/
這裏咱們想介紹的是開源自動更新框架NAppUpdate,NAppUpdate能很容易的和任何.Net桌面應用程序(包括WinForms應用應用和WPF應用程序)進行集成,針對版本訂閱、文件資源、更新任務提供了靈活方即可定製接口。並且可支持條件更新運行用戶實現無限制的更新擴展。
下面咱們也會經過一個具體的實例來講明怎麼在.NET桌面程序中使用NAppUpdate進行系統升級和更新。
3、NAppUpdate
NAppUpdate組件使用特定的xml文件來描述系統版本更新。Xml文件中包括下面內容,文件更新的描述信息、更新須要的特定邏輯、和更新須要執行的動做。
3.1 在項目中使用NAppUpdate
很簡單的就能集成到項目中:
(1)在項目添加NAppUpdate.Framework.dll引用。
(2)添加一個Class文件到項目進行更新檢查(具體參考後面實例)。
(3)根據版本更新的須要修改配置更新「源」。
(4)建立一個最小包含NAppUpdate.Framework.dll文件的運行包。
發佈更新:
(1)Build項目
(2)手工建立或使用NAppUpdate內置提供的FeedBuilder工具建立更新xml配置文件。
(3)把生成的文件放入服務器版本更新URL所在位置。
(4)建立一個最小包含NAppUpdate.Framework.dll文件的運行包。
3.2 NAppUpdate工做流程
NAppUpdate如何更新系統:
(1)根據不一樣種類的更新需求,系統應該首先建立一個UpdateManager的實例,例如若是使用SimpleWebSource這種類型的更新,則須要提供URL指向遠程版本發佈目錄。
(2)咱們的系統經過調用NAppUpdate組件的CheckForUpdates方法,獲取更新信息。
(3)NAppUpdate下載版本描述的XML文件,經過比較這個文件來肯定是否有版本須要更新。
(4)咱們的系統調用NAppUpdate方法PrepareUpdates來進行更新初始化,下載更新文件到臨時目錄。
(5)NAppUpdate解壓一個updater可執行文件(.exe)到臨時目錄中。
(6)最後,咱們系統調用NAppUpdate的方法ApplyUpdates(結束程序,執行更新,最後再啓動程序)。
4、實例代碼
4.1具體示例代碼
很簡單的就能集成到項目中:
(1)在項目添加NAppUpdate.Framework.dll引用。
(2)添加一個版本更新菜單。
(3)點擊菜單會彈出模式對話框,提示版本更新。
(3)點擊版本升級按鈕會進行版本升級,結束程序並從新啓動。
版本檢查:
private UpdateManager updManager; //聲明updateManager
在窗體的Onload事件中,建立一個後臺線程進行版本檢查,若是發現新版本則把版本描述信息顯示在窗體界面上。
this.IsPushVersion = AppContext.Context.AppSetting.IsPushVersionUpgrade;
var appUpgradeURL = ConfigurationManager.AppSettings["VersionUpdateURL"];
if (string.IsNullOrEmpty(appUpgradeURL))
{
this.MessageContent = "更新服務器URL配置爲空,請檢查修改更新配置。";
UpgradeEnabled = false;
return;
}
updManager = UpdateManager.Instance;
// Only check for updates if we haven't done so already
if (updManager.State != UpdateManager.UpdateProcessState.NotChecked)
{
updManager.CleanUp();
//return;
}
updManager.UpdateSource = PrepareUpdateSource(appUpgradeURL);
updManager.ReinstateIfRestarted();
try
{
updManager.CheckForUpdates();
}
catch(Exception ex)
{
UpgradeEnabled = false;
//LogManager.Write(ex);
this.MessageContent =
string.Format("版本更新服務器{0}鏈接失敗!n請檢查修改更新配置信息。", appUpgradeURL);
return;
}
if (updManager.UpdatesAvailable == 0)
{
var currentVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
this.MessageContent = string.Format("已是最新的版本{0}n沒有可用的更新。", currentVersion);
}
else
{
this.UpgradeEnabled = true;
updateTaskHelper = new UpdateTaskHelper();//自定義一個TaskHelper類負責合併處理版本描述信息。
var desc = updateTaskHelper.UpdateDescription;
var currentVersion = updateTaskHelper.CurrentVersion;
this.MessageContent = string.Format("有可更新的版本,更新文件數量: ({0})n版本描述:n{1} 。",
updManager.UpdatesAvailable, desc);
var taskInfo = this.updateTaskHelper.TaskListInfo;
}
VersionUpdateSource 是對SimpleWebSource實現的的一個簡單擴展。
private NAppUpdate.Framework.Sources.IUpdateSource PrepareUpdateSource(string url)
{
// Normally this would be a web based source.
// But for the demo app, we prepare an in-memory source.
var source = new VersionUpdateSource(url);
/
return source;
}
版本升級代碼:
使用UpdManager調用異步方法進行版本升級,而後結束程序並從新啓動。
updManager.BeginPrepareUpdates(
asyncResult =>
{
try
{
if (asyncResult.IsCompleted)
{
isBeginPrepareUpdates = false;
this.IsBusy = false;
//UpdateManager updManager = UpdateManager.Instance;
this.SettingsPageView.Dispatcher.Invoke(new Action(() =>
{
IsBusy = false;
var dr1 = DialogHelper.GetDialogWindow(
"安裝更新須要退出系統,請您務必保存好您的數據,n系統更新完成後再重新登陸,您肯定要如今更新系統嗎?",
CMessageBoxButton.OKCancel);
if (dr1 == CMessageBoxResult.OK)
{
// This is a synchronous method by design, make sure to save all user work before calling
// it as it might restart your application
//updManager.ApplyUpdates(true, true, true);
updManager.ApplyUpdates(true);
}
else
{
this.Cancel();
}
})
);
}
}
catch (Exception ex)
{
DialogHelper.GetDialogWindow("An error occurred while trying to install software updates",CMessageBoxButton.OK);
}
finally
{
updManager.CleanUp();
}
}, null);
(4)若是選中「開啓版本更新提示」,則程序啓動時會自動檢查新版本狀況,並提示給用戶,見下圖。
在系統啓動的時候,開啓一個後臺線程進行版本檢查,若是發現新版本則提示給用戶。
void bgWorkerVersionUpgrade_DoWork(object sender, DoWorkEventArgs e)
{
VersionUpgradeContent = string.Empty;
var updManager = UpdateManager.Instance;
// Only check for updates if we haven't done so already
if (updManager.State != UpdateManager.UpdateProcessState.NotChecked)
{
//DialogHelper.GetDialogWindow("Update process has already initialized; current state: " + updManager.State.ToString()
// , CMessageBoxButton.OK);
updManager.CleanUp();
//Foundation.Wpf.Toolkit.MessageBox.Show("Update process has already initialized; current state: " + updManager.State.ToString());
//return;
}
var appUpgradeURL = ConfigurationManager.AppSettings["VersionUpdateURL"];
updManager.UpdateSource = PrepareUpdateSource(appUpgradeURL);
updManager.ReinstateIfRestarted();
updManager.CheckForUpdates();
if (updManager.UpdatesAvailable != 0)
{
updateTaskHelper = new UpdateTaskHelper();
var desc = updateTaskHelper.UpdateDescription;
var currentVersion = updateTaskHelper.CurrentVersion;
this.VersionUpgradeContent = string.Format("更新文件數量: ({0})n更新內容:n{1} 。",
updManager.UpdatesAvailable, desc);
}
}
void bgWorkerVersionUpgrade_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if(e.Error == null && this.VersionUpgradeContent != string.Empty)
{
this.View.ShowNotifyWindow("請升級新版本", this.VersionUpgradeContent, Hardcodet.Wpf.TaskbarNotification.BalloonIcon.Warning);
}
//throw new NotImplementedException();
}
4.2版本發佈xml配置文件示例
版本更新可對不一樣類型的文件更新,設置不一樣類型的條件,這些條件(文件大小檢查、文件版本檢查、文件更新日期、文件是否存在,操做系統版本,文件完整性檢查等)可靈活組合使用。
<Feed>
<Tasks>
<FileUpdateTask hotswap="true" updateTo="http://SomeSite.com/Files/NewVersion.txt" localPath="CurrentVersion.txt">
<Description>Fixes a bug where versions should be odd numbers.</Description>
<Conditions>
<FileChecksumCondition checksumType="sha256" checksum="6B00EF281C30E6F2004B9C062345DF9ADB3C513710515EDD96F15483CA33D2E0" />
</Conditions>
</FileUpdateTask>
</Tasks>
</Feed>
4.3實現更新數據庫
咱們項目中使用的是MySql數據庫,用戶想功過sql文件的方式來更新數據。
實現流程:
(1)把數據庫更新以sql文本文件的方式發佈到版本服務器,設置更新時間(根據文件自己建立時間)
<Conditions>
<FileDateCondition what="older" timestamp="2017/12/5" />
</Conditions>
(2)客戶端會根據實際判斷是否須要下載這個更新。
(3)爲了安全和防止文件被篡改,能夠考慮對文件進行加密解密和完整性校驗。
<FileChecksumCondition checksumType="sha256" checksum="6B00EF281C30E6F2004B9C062345DF9ADB3C513710515EDD96F15483CA33D2E0" />
(4)使用NAppUpdate下載sql文件到客戶端特定的目錄中。
(5)程序啓動時獲取mysql鏈接信息,打開一個後臺進程,調用mysql工具,使用隱藏的命令行執行sql文件。
(6)存儲以及執行過的sql文件信息到數據庫(做爲記錄和避免重複執行)。
示例代碼:
//開進程執行sql文件
private static void UpdateMySqlScript(IList<string> sqlFiles,IVersionUpgradLogService versionService)
{
string connStr = LoadMySqlConnectionString();
DbConnectionSetting db = GetDBConnectionSetting(connStr);
foreach(var sqlFile in sqlFiles)
{
if (string.IsNullOrWhiteSpace(sqlFile) || string.IsNullOrEmpty(sqlFile))
{
continue;
}
UpdateDBByMySql(db, sqlFile);
versionService.ExecuteSqlFile(GetFileName(sqlFile));
}
}
private static void UpdateDBByMySql(DbConnectionSetting db, string sqlFile)
{
Process proc = new Process();
proc.StartInfo.FileName = "cmd.exe"; // 啓動命令行程序
proc.StartInfo.UseShellExecute = false; // 不使用Shell來執行,用程序來執行
proc.StartInfo.RedirectStandardError = true; // 重定向標準輸入輸出
proc.StartInfo.RedirectStandardInput = true;
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.CreateNoWindow = true; // 執行時不建立新窗口
proc.StartInfo.WorkingDirectory = GetWorkingDirectory(@"offlineappmysqlbin");
proc.Start();
proc.StandardInput.WriteLine(""mysql.exe" -h " + db.Server + " -u" + db.User + " -p" + db.PWD + " --default-character-set=" + "utf8" + " " + db.DBName + " <"" + sqlFile + """);
proc.StandardOutput.ReadLine();
proc.Close();
}
//獲取須要執行的sql文件
private IList<string> GetNeedToRunSqlFiles(IVersionUpgradLogService versionService)
{
IList<string> result = new List<string>();
string updateSqlScriptFolder = "updatesqlscripts";
string folder = Path.Combine( LoadApplicationPath(),updateSqlScriptFolder);
var files = Directory.GetFiles(folder, "*.*", SearchOption.AllDirectories)
.Where(s => s.EndsWith(".sql") );
var lockedFiles = Directory.GetFiles(folder, "*.lock", SearchOption.AllDirectories);
foreach(var file in files)
{
if(versionService.IsExecute(GetFileName(file)))
{
continue;
}
result.Add(file);
}
return result;
}
//記錄sql執行狀況的服務
public class VersionUpgradLogService :IVersionUpgradLogService
{
private readonly IVersionUpgradLogRepository versionUpgradLogRepository;
private IList<string> cachedVersionFileNames;
public VersionUpgradLogService(IVersionUpgradLogRepository versionUpgradLogRepository)
{
this.versionUpgradLogRepository = versionUpgradLogRepository;
this.cachedVersionFileNames = LoadAllExecutedFileNames();
}
private IList<string> LoadAllExecutedFileNames()
{
IList<string> result = new List<string>();
var dtoList = versionUpgradLogRepository.GetAll();
foreach(var item in dtoList)
{
if(HasExecuted(item))
{
if (!result.Contains(item.Name))
{
result.Add(item.Name);
}
}
}
return result;
}
private bool HasExecuted(VersionUpgradLogDTO item)
{
return item.Status == 1 & item.UpgradType == 0;
}
public IList<VersionUpgradLog> GetAllByTaskId(string taskId)
{
return DomainAdapter.Adapter.Adapt<IList<VersionUpgradLogDTO>, List<VersionUpgradLog>>(
versionUpgradLogRepository.GetAllByTaskId(taskId));
}
public bool IsExecute(string fileName)
{
if(cachedVersionFileNames.Contains(fileName))
{
return true;
}
return false;
}
public void ExecuteSqlFile(string fileName)
{
VersionUpgradLog versionLog = new VersionUpgradLog();
versionLog.UpgradType = 0;
versionLog.UpdateDate = System.DateTime.Now;
versionLog.Status = 1;
versionLog.Version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
versionLog.Name = fileName;
var item = DomainAdapter.Adapter.Adapt<VersionUpgradLog, VersionUpgradLogDTO>(versionLog);
versionUpgradLogRepository.Add(item);
}
}
}
Sql版本升級數據表