由于现在做的系统是基于Winform的,在服务器上需要发布,挺麻烦的,而且改动一点点就得全部上传,忍耐了很久终于做了一个一键发布的工具。原理跟目录同步差不多,只不过要做一些特殊处理。先获取本地的最大版本号,再请求服务器更改,服务器把上面最新的版本复制一份到新版本文件夹(目的是保留老版本)。然后再跟目录同步工具一样,但要排除之前的版本文件夹。
同样,需要一个服务端ashx文件配合
using System; using System.Collections.Generic; using System.IO; using System.Web; using System.Linq; using System.Xml.Serialization; namespace WebUpdate { /// /// AppPublish 的摘要说明 /// public class AppPublish : IHttpHandler { private HttpRequest Request; private HttpResponse Response; public void ProcessRequest(HttpContext context) { Request = context.Request; Response = context.Response; context.Response.ContentType = "text/plain"; try { if (!String.IsNullOrEmpty(Request["remotePath"])) { //复制 if (Request["action"] == "setmaxversion") { string dir = Path.GetDirectoryName(context.Server.UrlDecode(Request["remotePath"])); if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); Response.Write("ok"); return; } string maxversion = GetMaxVersion(dir); if (maxversion != null) { if (maxversion != Request["maxversion"]) { //复制为最大版本 FileHelper.Copy(Path.Combine(dir, "Application Files", maxversion), Path.Combine(dir, "Application Files", Request["maxversion"])); } } Response.Write("ok"); } //列出数据 if (Request["action"] == "dir") { string appDir = context.Server.UrlDecode(Request["remotePath"]); if (!Directory.Exists(appDir)) { throw new Exception("远程目录不存在"); } List list = new List(); LoadFile(list, appDir, appDir, false); string maxversion = GetMaxVersion(appDir); if (maxversion != null) { LoadFile(list, Path.Combine(appDir, "Application Files", maxversion), appDir, true); } XmlSerializer sz = new XmlSerializer(list.GetType()); sz.Serialize(Response.OutputStream, list); } //上传 if (Request["action"] == "upload") { string filePath = context.Server.UrlDecode(Request["remotePath"]); string dir = Path.GetDirectoryName(filePath); if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); } Request.Files[0].SaveAs(filePath); Response.Write("上传成功" + filePath); } } } catch (Exception ex) { Response.Write(ex.Message); } } public class FileMessage { public long Size { get; set; } public string Sha1 { get; set; } public string FileName { get; set; } } private void LoadFile(List list, string p, string baseDir, bool isLocaChild) { foreach (string file in Directory.GetFiles(p)) { list.Add(GetFileInfo(file, baseDir)); } if (isLocaChild) { foreach (string folder in Directory.GetDirectories(p)) { LoadFile(list, folder, baseDir, isLocaChild); } } } private string GetMaxVersion(string dirPath) { List list = new List(); if (!Directory.Exists(Path.Combine(dirPath, "Application Files"))) { Directory.CreateDirectory(Path.Combine(dirPath, "Application Files")); } foreach (string dir in Directory.GetDirectories(Path.Combine(dirPath, "Application Files"))) { list.Add(Path.GetFileName(dir)); } if (list.Count > 0) { return list.Max(); } return null; } /// /// 获取文件信息 /// private FileMessage GetFileInfo(String filePath, string baseDir) { FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); long lenth = fs.Length; System.Security.Cryptography.HashAlgorithm algorithm = System.Security.Cryptography.SHA1.Create(); String result = BitConverter.ToString(algorithm.ComputeHash(fs)).Replace("-", ""); fs.Close(); return new FileMessage { Size = lenth, Sha1 = result, FileName = filePath.Replace(baseDir, "").TrimStart('\\').TrimStart('/') }; } public bool IsReusable { get { return false; } } } }
xml配置文件
http://leeup.fyj.me/AppPublish.ashx C:\发布\HAK-leemap\ E:\HAK\Setup\
处理逻辑
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using System.Linq; using System.Net; using System.Text; using System.Threading; using System.Windows.Forms; using System.Xml; using System.Xml.Serialization; namespace AppPublish { public partial class MainForm : Form { public MainForm() { InitializeComponent(); } public delegate void Action(); public void SafeCall(Control ctrl, Action callback) { if (ctrl.InvokeRequired) ctrl.Invoke(callback); else callback(); } private void MainForm_Load(object sender, EventArgs e) { Control.CheckForIllegalCrossThreadCalls = false; XmlDocument doc = new XmlDocument(); doc.Load(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "publish.config.xml")); List list = new List(); foreach (XmlNode node in doc.SelectNodes("/publish/app")) { list.Add(node.Attributes["name"].InnerText); } this.comboBox1.DataSource = list; } /// /// 获取本地最大版本 /// /// /// private string GetLocalMaxVersion(string localPath) { List list = new List(); foreach (string dir in Directory.GetDirectories(Path.Combine(localPath, "Application Files"))) { list.Add(Path.GetFileName(dir)); } if (list.Count > 0) { return list.Max(); } return null; } private void btnPublish_Click(object sender, EventArgs e) { this.btnPublish.Text = "发布中..."; this.btnPublish.Enabled = false; XmlDocument doc = new XmlDocument(); doc.Load(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "publish.config.xml")); XmlNode appnode = doc.SelectSingleNode("/publish/app[@name='" + this.comboBox1.Text + "']"); string publishUrl = appnode.SelectSingleNode("publishUrl").InnerText; string localFolder = null; if (appnode.SelectSingleNode("localFolder") != null) { localFolder = appnode.SelectSingleNode("localFolder").InnerText; } string remoteFolder = null; if (appnode.SelectSingleNode("remoteFolder") != null) { remoteFolder = appnode.SelectSingleNode("remoteFolder").InnerText; } XmlNodeList nlist = appnode.SelectNodes("file"); this.progressBar1.Value = 0; ThreadPool.QueueUserWorkItem((o) => { try { this.txtStatus.Text = "正在获取本地信息..."; //本地文件信息 List localList = new List(); LoadFile(localList, localFolder, localFolder, false); string maxversion = GetLocalMaxVersion(localFolder); if (maxversion != null) { LoadFile(localList, Path.Combine(localFolder, "Application Files", maxversion), localFolder, true); } this.txtStatus.Text = "正在请求服务器更改最大版本..."; WebClient wc = new WebClient(); string result = wc.DownloadString(publishUrl + "?remotePath=" + System.Web.HttpUtility.UrlEncode(remoteFolder) + "&action=setmaxversion&maxversion=" + maxversion); if (result.Trim() != "ok") { this.SafeCall(this.listBox1, () => { this.listBox1.Items.Add("请求服务器更改最大版本错误:server返回" + result); }); this.txtStatus.Text = "同步出错"; return; } this.txtStatus.Text = "正在从服务器获取数据..."; XmlSerializer sz = new XmlSerializer(typeof(List)); //服务器文件信息 List serverList = (List)sz.Deserialize(wc.OpenRead(publishUrl + "?remotePath=" + System.Web.HttpUtility.UrlEncode(remoteFolder) + "&action=dir")); this.txtStatus.Text = "正在处理..."; //需要同步的文件列表 List syncList = new List(); foreach (FileMessage fm in localList) { FileMessage srcFm = GetFileMessage(serverList, fm.FileName); //如果服务器上不存在该文件 则需同步 if (srcFm == null) { string url = publishUrl + "?remotePath=" + System.Web.HttpUtility.UrlEncode(Path.Combine(remoteFolder, fm.FileName)) + "&action=upload"; syncList.Add(new UploadMessage { UploadUrl = url, LocalPath = Path.Combine(localFolder, fm.FileName), RemoteFileName = Path.Combine(remoteFolder, fm.FileName), Size = fm.Size }); } else { //如果不等于服务器上的sha1 则需要同步 if (!srcFm.Sha1.Equals(fm.Sha1, StringComparison.CurrentCultureIgnoreCase)) { string url = publishUrl + "?remotePath=" + System.Web.HttpUtility.UrlEncode(Path.Combine(remoteFolder, fm.FileName)) + "&action=upload"; syncList.Add(new UploadMessage { UploadUrl = url, LocalPath = Path.Combine(localFolder, fm.FileName), RemoteFileName = Path.Combine(remoteFolder, fm.FileName), Size = fm.Size }); } } } foreach (FileMessage fm in serverList) { FileMessage destFm = GetFileMessage(localList, fm.FileName); //如果客户端不存在该文件 则需删除服务器上的 if (destFm == null) { IDictionary dic = new Dictionary(); string url = publishUrl + "?remotePath=" + System.Web.HttpUtility.UrlEncode(Path.Combine(remoteFolder, fm.FileName)) + "&action=upload"; syncList.Add(new UploadMessage { UploadUrl = url, RemoteFileName = Path.Combine(remoteFolder, fm.FileName), IsDelete = true }); } } if (syncList.Count == 0) { MessageBox.Show("已经同步好了。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); this.btnPublish.Text = "发布"; this.btnPublish.Enabled = true; this.txtStatus.Text = "就绪"; return; } //计算总同步大小 long total = 0; foreach (UploadMessage d in syncList) { total += d.Size; } this.SafeCall(this.progressBar1, () => { this.progressBar1.Maximum = (int)total; }); foreach (UploadMessage mess in syncList) { try { if (mess.IsDelete) { //不删除服务器根目录 if (mess.RemoteFileName.Contains("Application Files")) { this.txtStatus.Text = "正在删除服务器文件" + mess.RemoteFileName; wc = new WebClient(); string d = wc.DownloadString(mess.UploadUrl); this.SafeCall(this.listBox1, () => { this.listBox1.Items.Add(d); }); continue; } } else { string localFile = mess.LocalPath; this.txtStatus.Text = "正在上传" + localFile; string url = mess.UploadUrl; //WebClient wb = new WebClient(); //byte[] b = wb.UploadFile(url, localFile); //string result = System.Text.Encoding.UTF8.GetString(b); //this.SafeCall(this.listBox1, () => //{ // this.listBox1.Items.Add(result); //}); HttpUploadHelper up = new HttpUploadHelper(); up.UploadStenEvent += up_UploadStenEvent; up.CompleteEvent += up_CompleteEvent; up.Upload_Request(url, localFile, mess.RemoteFileName); } } catch (Exception ex) { this.SafeCall(this.listBox1, () => { this.listBox1.Items.Add(ex.Message); }); this.SafeCall(this.txtErrorCount, () => { this.txtErrorCount.Text = (Convert.ToInt32(txtErrorCount.Text) + 1).ToString(); }); } } MessageBox.Show("发布完成。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); this.txtStatus.Text = "就绪"; this.btnPublish.Text = "发布"; this.btnPublish.Enabled = true; } catch (Exception ex) { this.SafeCall(this.listBox1, () => { this.listBox1.Items.Add(ex.Message); }); this.txtStatus.Text = "同步出错"; this.btnPublish.Text = "发布"; this.btnPublish.Enabled = true; } }); } void up_CompleteEvent(string result) { this.SafeCall(this.listBox1, () => { this.listBox1.Items.Add(result); }); } /// /// 获取文件信息 /// private FileMessage GetFileInfo(String filePath, string baseDir) { FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); long lenth = fs.Length; System.Security.Cryptography.HashAlgorithm algorithm = System.Security.Cryptography.SHA1.Create(); String result = BitConverter.ToString(algorithm.ComputeHash(fs)).Replace("-", ""); fs.Close(); return new FileMessage { Size = lenth, Sha1 = result, FileName = filePath.Replace(baseDir, "") }; } private void LoadFile(List list, string p, string baseDir, bool isIncludeChild) { foreach (string file in Directory.GetFiles(p)) { if (file.Contains("publish.config.xml")) { continue; } list.Add(GetFileInfo(file, baseDir)); } if (isIncludeChild) { foreach (string folder in Directory.GetDirectories(p)) { LoadFile(list, folder, baseDir, isIncludeChild); } } } private FileMessage GetFileMessage(List list, string fileName) { foreach (FileMessage fm in list) { if (fm.FileName.Equals(fileName, StringComparison.CurrentCultureIgnoreCase)) { return fm; } } return null; } private void btnClear_Click(object sender, EventArgs e) { this.listBox1.Items.Clear(); //HttpUploadHelper up = new HttpUploadHelper(); //up.UploadStenEvent += up_UploadStenEvent; //up.Upload_Request("http://www.leemap.com:9999/AppPublish.ashx?remotePath=" + System.Web.HttpUtility.UrlEncode("e:\\iTran.CargoPlus.Face.exe"), @"D:\ZUHCargoPlus\iTran.CargoPlus.Face\bin\Debug\iTran.CargoPlus.Face.exe", "iTran.CargoPlus.Face.exe"); } void up_UploadStenEvent(UploadEvent e) { this.SafeCall(this.progressBar1, () => { this.progressBar1.Value = this.progressBar1.Value += e.CurrentSize; }); this.SafeCall(this.txtSizeStatus, () => { this.txtSizeStatus.Text = this.progressBar1.Value + "/" + this.progressBar1.Maximum; }); } protected override void WndProc(ref Message SystemMessage) { switch (SystemMessage.Msg) { case 0x0112: if (((int)SystemMessage.WParam) == 61536) { // this.Owner.Close(); Application.ExitThread(); } else { base.WndProc(ref SystemMessage); } break; default: base.WndProc(ref SystemMessage); break; } } private void comboBox1_SelectedIndexChanged(object sender, EventArgs e) { } } public class UploadMessage { public long Size { get; set; } public string Sha1 { get; set; } public string LocalPath { get; set; } public string UploadUrl { get; set; } public string RemoteFileName { get; set; } public bool IsDelete { get; set; } } public class FileMessage { public long Size { get; set; } public string Sha1 { get; set; } private string _fileName; public string FileName { get { return (_fileName == null ? null : _fileName.TrimStart('\\').TrimStart('/')); } set { _fileName = value; } } } }