为什么开发这个工具呢,因为自己网站的图片是存储在阿里云上的,但是后台管理确实直接传到本地的,不直接传到阿里云,因为网络不好,上传会很费时。
阿里云有提供官方的sdk工具,不过没有达到我想要的效果,我就是简单的本地文件是什么结构阿里云上就是怎么样的。之前是按数据库来查询的,因为我上传文件数据库会有记录,还有个字段标志是否上传到阿里云,不过后来改善后不需要数据库,直接做成了文件系统同步。
原理很简单,就是递归本地目录,记录文件的路径和MD5值,再去阿里云查询如果不存在则上传,如果MD5不一样也上传,如果阿里云有但是本地没有则可以删除阿里云的文件(可选)。
代码还是有点多,我用WPF做的,需要特别注意多线程问题
xaml文件代码
<Window x:Class="Blogs.Tools.FileManager.AliyunSyncWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:con="clr-namespace:FYJ.Winui.Controls;assembly=FYJ.Winui" Title="阿里云同步工具" Height="400" Width="640"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="120"></RowDefinition> <RowDefinition Height="40"></RowDefinition> <RowDefinition Height="*"></RowDefinition> <RowDefinition Height="50"></RowDefinition> </Grid.RowDefinitions> <StackPanel Grid.Row="0" Margin="0,10,0,0"> <WrapPanel> <TextBlock Text="本地目录:"></TextBlock> <TextBox Width="200" Margin="10,0,0,0" x:Name="txtLocalPath" Text="D:\Git\Blogs\Blogs.UI.Static\images-drive"></TextBox> <Button x:Name="btnBrower" Content="..." Click="btnBrower_Click" Margin="10,0,0,0"></Button> </WrapPanel> <WrapPanel Margin="0,10,0,0"> <TextBlock Text="Bucket:"></TextBlock> <con:ComboBoxEx Width="200" Margin="10,0,0,0" x:Name="txtBucket"></con:ComboBoxEx> </WrapPanel> <WrapPanel Margin="0,10,0,0"> <CheckBox Content="是否删除阿里云多余文件" Margin="10,0,0,0" x:Name="chkIsDelete"></CheckBox> <Button Content="同步文件到阿里云存储" Margin="10,0,0,0" x:Name="btnSyncAliyun" Click="btnSyncAliyun_Click"></Button> <Button Content="删除阿里云空目录" Margin="10,0,0,0" x:Name="btnClearNullFolder" Click="btnClearNullFolder_Click"></Button> <TextBlock Text="错误数:" Margin="30,0,0,0"></TextBlock> <TextBlock Text="0" Foreground="#FFF30707" Margin="10,0,0,0" x:Name="txtError"/> </WrapPanel> </StackPanel> <StackPanel Grid.Row="1"> <ProgressBar Height="40" x:Name="progressBar1"></ProgressBar> </StackPanel> <con:LogListBox Grid.Row="2" x:Name="logList1"></con:LogListBox> <TextBlock Grid.Row="3" Text="就绪" x:Name="txtStatus"></TextBlock> </Grid> </Window>
后台代码
using Blogs.Tools.Util; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; namespace Blogs.Tools.FileManager { /// <summary> /// AliyunSyncWindow.xaml 的交互逻辑 /// </summary> public partial class AliyunSyncWindow : Window { public AliyunSyncWindow() { InitializeComponent(); } protected override void OnInitialized(EventArgs e) { base.OnInitialized(e); List<string> list = new List<string> { "images-drive", "album-drive", "files-drive" }; this.txtBucket.ItemsSource = list; this.txtBucket.SelectedIndex = 0; } private void btnBrower_Click(object sender, RoutedEventArgs e) { System.Windows.Forms.FolderBrowserDialog diag = new System.Windows.Forms.FolderBrowserDialog(); if (diag.ShowDialog() == System.Windows.Forms.DialogResult.OK) { txtLocalPath.Text = diag.SelectedPath; } } private async void btnSyncAliyun_Click(object sender, RoutedEventArgs e) { if (txtLocalPath.Text.Trim() == "") { MessageBox.Show("请选择本地路径"); return; } if (txtBucket.Text.Trim() == "") { MessageBox.Show("请输入Bulcket"); return; } if (System.IO.Path.GetFileName(txtLocalPath.Text) != txtBucket.Text) { MessageBox.Show("选择的Bulcket与路径不一致"); return; } string localPatch = txtLocalPath.Text.Trim(); string bucket = txtBucket.Text.Trim(); bool isDelete = chkIsDelete.IsChecked.Value; await Task.Factory.StartNew(() => { #region 处理同步 //需要同步的文件列表 this.Dispatcher.Invoke(() => { txtStatus.Text = "正在同步获取需要同步的信息..."; }); List<UploadMessage> syncList = new List<UploadMessage>(); GetSyncList(bucket, "", syncList, localPatch); this.Dispatcher.Invoke(() => { this.txtError.Text = "0"; }); this.Dispatcher.Invoke(() => { this.progressBar1.Maximum = syncList.Count; }); for (int i = 0; i < syncList.Count; i++) { UploadMessage up = syncList[i]; this.Dispatcher.Invoke(() => { txtStatus.Text = "正在同步" + up.Key + "..."; }); if (up.IsDelete) { if (isDelete) { try { AliyunHelper.DeleteObject(bucket, up.Key); this.logList1.AddItem("成功删除" + bucket + "/" + up.Key); } catch (Exception ex) { this.Dispatcher.Invoke(() => { this.logList1.AddItem("删除失败" + ex.Message); this.txtError.Text = (Convert.ToInt32(this.txtError.Text) + 1).ToString(); }); } } } else { //上传文件到阿里云 FileStream stream = null; try { stream = new FileStream(System.IO.Path.Combine(localPatch, up.Key), FileMode.Open); AliyunHelper.PutObject(stream, bucket, up.Key); this.Dispatcher.Invoke(() => { this.logList1.AddItem("成功上传" + bucket + "/" + up.Key); }); } catch (Exception ex) { this.Dispatcher.Invoke(() => { this.logList1.AddItem("上传失败" + ex.Message); this.txtError.Text = (Convert.ToInt32(this.txtError.Text) + 1).ToString(); }); } finally { if (stream != null) { stream.Close(); } } } this.Dispatcher.Invoke(() => { this.progressBar1.Value = i + 1; }); } #endregion }); txtStatus.Text = "完成"; } /// <summary> /// 获取需要同步的信息 /// </summary> /// <param name="bucket"></param> /// <param name="key"></param> /// <param name="syncList"></param> /// <param name="rootLocalPath"></param> private void GetSyncList(string bucket, string key, List<UploadMessage> syncList, string rootLocalPath) { string aliyunEndpoint = System.Configuration.ConfigurationManager.AppSettings["aliyunEndpoint"]; string aliyunKey = System.Configuration.ConfigurationManager.AppSettings["aliyunKey"]; string aliyunSecret = System.Configuration.ConfigurationManager.AppSettings["aliyunSecret"]; Aliyun.OpenServices.OpenStorageService.OssClient ossClient = new Aliyun.OpenServices.OpenStorageService.OssClient(aliyunEndpoint, aliyunKey, aliyunSecret); Aliyun.OpenServices.OpenStorageService.ListObjectsRequest request = new Aliyun.OpenServices.OpenStorageService.ListObjectsRequest(bucket); request.MaxKeys = 1000; request.Prefix = key; if (key != "") { request.Prefix = key.TrimEnd(new char[] { '\\', '/' }) + "/"; } request.Delimiter = "/"; var o = ossClient.ListObjects(request); var list = o.ObjectSummaries; List<string> localFiles = new List<string>(); foreach (string file in Directory.GetFiles(System.IO.Path.Combine(rootLocalPath, key))) { FileMessage local = GetFileInfo(file, rootLocalPath); localFiles.Add(local.Key); //如果服务器上存在该文件并且MD5值相等 if (list.Where(c => c.Key == local.Key && c.ETag == local.MD5).Count() == 1) { continue; } else { syncList.Add(new UploadMessage { Key = local.Key, Bucket = bucket, IsDelete = false, MD5 = local.MD5 }); } } foreach (var item in list) { //如果本地不包含服务器上的文件 则删除服务器上的文件 if (!localFiles.Contains(item.Key)) { syncList.Add(new UploadMessage { Key = item.Key, Bucket = bucket, IsDelete = true, MD5 = item.ETag }); } } //递归 foreach (string folder in Directory.GetDirectories(System.IO.Path.Combine(rootLocalPath, key))) { GetSyncList(bucket, folder.Replace(rootLocalPath, "").TrimStart(new char[] { '/', '\\' }).Replace("\\", "/"), syncList, rootLocalPath); } } private FileMessage GetFileMessage(List<FileMessage> list, string key) { foreach (FileMessage fm in list) { if (fm.Key.Equals(key, StringComparison.CurrentCultureIgnoreCase)) { return fm; } } return null; } /// <summary> /// 获取文件信息 /// </summary> 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.MD5.Create(); String result = BitConverter.ToString(algorithm.ComputeHash(fs)).Replace("-", ""); fs.Close(); return new FileMessage { Size = lenth, MD5 = result, Key = filePath.Replace(baseDir, "") }; } private async void btnClearNullFolder_Click(object sender, RoutedEventArgs e) { if (txtBucket.Text.Trim() == "") { MessageBox.Show("请输入Bulcket"); return; } string bucket = txtBucket.Text.Trim(); //服务器文件信息 List<FileMessage> folderList = new List<FileMessage>(); List<FileMessage> fileList = new List<FileMessage>(); txtStatus.Text = "正在处理..."; await Task.Factory.StartNew(() => { ClearNullFolder(bucket, ""); }); txtStatus.Text = "完成"; } private void ClearNullFolder(string bucket, string key) { string aliyunEndpoint = System.Configuration.ConfigurationManager.AppSettings["aliyunEndpoint"]; string aliyunKey = System.Configuration.ConfigurationManager.AppSettings["aliyunKey"]; string aliyunSecret = System.Configuration.ConfigurationManager.AppSettings["aliyunSecret"]; Aliyun.OpenServices.OpenStorageService.OssClient ossClient = new Aliyun.OpenServices.OpenStorageService.OssClient(aliyunEndpoint, aliyunKey, aliyunSecret); Aliyun.OpenServices.OpenStorageService.ListObjectsRequest request = new Aliyun.OpenServices.OpenStorageService.ListObjectsRequest(bucket); request.MaxKeys = 1000; request.Prefix = key; request.Delimiter = "/"; var list = ossClient.ListObjects(request); if (list.CommonPrefixes.Count() == 0) { if (list.ObjectSummaries.Count() == 0 || (list.ObjectSummaries.Count() == 1 && list.ObjectSummaries.First().Key == key) ) { try { AliyunHelper.DeleteObject(bucket, key); this.logList1.AddItem("成功删除" + bucket + "/" + key); } catch (Exception ex) { this.logList1.AddItem("删除失败" + ex.Message); } } } foreach (var v in list.CommonPrefixes) { ClearNullFolder(bucket, v); } } } public class FileMessage { public long Size { get; set; } public string MD5 { get; set; } private string _key; public string Key { get { return (_key == null ? null : _key.TrimStart('\\').TrimStart('/').Replace("\\", "/")); } set { _key = value; } } } public class UploadMessage { public long Size { get; set; } public string MD5 { get; set; } public string LocalPath { get; set; } public string Key { get; set; } public string Bucket { get; set; } public bool IsDelete { get; set; } } }