using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Net.Http; using System.Text; using System.Timers; using System.Windows; using System.Windows.Input; using System.Windows.Threading; using HeBianGu.Base.WpfBase; using HeBianGu.Control.Explorer; using HeBianGu.Control.Message; using HeBianGu.Service.Mvc; using Hopetry.Models; using Hopetry.Provider; using Hopetry.Provider.Behaviors; using Hopetry.Services; using Hopetry.ViewModel.Send; using Microsoft.Extensions.Configuration; using Microsoft.Win32; using Microsoft.WindowsAPICodePack.Dialogs; using Minio; using Minio.DataModel; using Minio.DataModel.Args; using Timer = System.Timers.Timer; using System.Net.Http; using System.Net; using Minio.Exceptions; using System.Collections.Concurrent; using System.Security.AccessControl; using Microsoft.VisualBasic; namespace HeBianGu.App.Disk { [ViewModel("Loyout")] internal class LoyoutViewModel : MvcViewModelBase { #region 参数定义及初始化 private readonly FileUploadService _uploadService; private string _path; /// 说明 public string Path { get { return _path; } set { _path = value; RaisePropertyChanged("Path"); } } private string _nearPath; /// 说明 public string NearPath { get { return _nearPath; } set { _nearPath = value; RaisePropertyChanged("NearPath"); } } private string _sharePath; /// 说明 public string SharePath { get { return _sharePath; } set { _sharePath = value; RaisePropertyChanged("SharePath"); } } protected override void Init() { Path = Environment.GetFolderPath(Environment.SpecialFolder.MyComputer); NearPath = Environment.GetFolderPath(Environment.SpecialFolder.Recent); SharePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop); // LinkActions.Add(new LinkAction() { Action = "Near", Controller = "Loyout", DisplayName = "最近使用", Logo = "\xe6f3" }); LinkActions.Add(new LinkAction() { Action = "Explorer", Controller = "Loyout", DisplayName = "全部文件", Logo = "\xe8d6" }); // LinkActions.Add(new LinkAction() { Action = "Image", Controller = "Loyout", DisplayName = " 图片", Logo = "" }); // LinkActions.Add(new LinkAction() { Action = "Video", Controller = "Loyout", DisplayName = " 视频", Logo = "" }); // LinkActions.Add(new LinkAction() { Action = "Document", Controller = "Loyout", DisplayName = " 文档", Logo = "" }); // LinkActions.Add(new LinkAction() { Action = "Music", Controller = "Loyout", DisplayName = " 音乐", Logo = "" }); // LinkActions.Add(new LinkAction() { Action = "Explorer", Controller = "Loyout", DisplayName = " 种子", Logo = "" }); //LinkActions.Add(new LinkAction() { Action = "Recent", Controller = "Loyout", DisplayName = " 其他", Logo = "" }); // LinkActions.Add(new LinkAction() { Action = "Space", Controller = "Loyout", DisplayName = "隐藏空间", Logo = "\xe613" }); //LinkActions.Add(new LinkAction() { Action = "Share", Controller = "Loyout", DisplayName = "我的分享", Logo = "\xe764" }); // LinkActions.Add(new LinkAction() { Action = "Near", Controller = "Loyout", DisplayName = "回收站", Logo = "\xe618" }); Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() => { SelectLink = LinkActions[0]; })); } protected override void Loaded(string args) { GetCompletedFiles(); } #endregion #region 文件上传 #region 参数定义 private SendViewModel _sendViewModel; private IConfiguration config; private SemaphoreSlim _semaphore = new SemaphoreSlim(5); private Timer _progressTimer; private Timer _heartbeatTimer ; private bool _isTimerRunning = false; private object _timerLock = new object(); private bool _isUploading = false; // 新增:标记是否有上传任务正在运行 private Task _currentUploadTask = null; // 新增:当前上传任务 private CancellationTokenSource _uploadCancellation = new CancellationTokenSource(); private int _uploadCount = 0; private int _completeCount = 0; private int regionCount = 0; private ExplorerMinIOBehavior _explorerBehavior; private MinIOSearchBehavior _minioBehavior; private Hopetry.Provider.SystemInfo systemInfo; // 配置Redis连接 private RedisService redis = new RedisService("175.27.168.120:6050,password=HopetryRedis1406,connectRetry=3"); public ICommand UploadCommand { get; } public ICommand UploadCommand1 { get; } public ICommand CreateFolderCommand { get; } public ICommand DownloadCommand { get; set; } public ICommand DeleteFolderCommand { get; } //public ICommand SelectItemsCommand { get; } public LoyoutViewModel(SendViewModel sendViewModel) { _sendViewModel = sendViewModel; _uploadService = new FileUploadService(); UploadCommand = new AsyncRelayCommand(async () => await UploadFile()); UploadCommand1 = new AsyncRelayCommand(async () => await UploadFile1()); CreateFolderCommand = new AsyncRelayCommand(async () => await CreateNewFolderAsync()); DeleteFolderCommand = new AsyncRelayCommand(async () => await DeleteSelectedItemsAsync()); DownloadCommand = new AsyncRelayCommand(async () => await DoDownloadCommand()); SelectDirCommand = new CustomCommand(async () => await DoSelectDirCommand()); systemInfo = SystemInfoCollector.Collect(); //SelectItemsCommand = new AsyncRelayCommand(async () => await RelayCommand(SelectItem)SelectItem()); // 初始化Timer _progressTimer = new Timer(1000); _progressTimer.Elapsed += UpdateProgress; _heartbeatTimer = new Timer(30_000); _heartbeatTimer.Elapsed += async (s, e) => await SendHeartbeatAsync(); _sendViewModel.UpLoadItems.CollectionChanged += (s, e) => { _sendViewModel.UpdateUploadingItems(); }; //加载上传完成列表 GetCompletedFiles(); } private async Task DoSelectDirCommand() { CommonOpenFileDialog dialog = new CommonOpenFileDialog { IsFolderPicker = true }; if (dialog.ShowDialog() == CommonFileDialogResult.Ok) { var folderPath = dialog.FileName; // 获取选中的文件夹路径 // todo 设置文件夹 Application.Current.Dispatcher.Invoke(() => { DownFolderSelect.DownFolder = folderPath; Console.WriteLine($"选择文件夹:{folderPath}"); }); // 处理选中的文件夹路径 } } #endregion #region 行为方法 // 添加设置Behavior的方法 public void SetExplorerBehavior(ExplorerMinIOBehavior behavior) { _explorerBehavior = behavior; } public void SetExplorerBehavior(MinIOSearchBehavior behavior) { _minioBehavior = behavior; } #endregion #region 更新总进度条 每隔1s更新一次 private void UpdateProgress(object sender, ElapsedEventArgs e) { if (_sendViewModel.UpLoadItems.Count == 0) return; double currentBytes = _sendViewModel.UpLoadItems.Sum(r => r.Double2); double totalBytes = _sendViewModel.UpLoadItems.Sum(r => r.Double1); double progress = Math.Round((currentBytes / totalBytes) * 100, 2); Application.Current.Dispatcher.Invoke(() => { _sendViewModel.Progress = progress; }); } //上传redis心跳检测 private async Task SendHeartbeatAsync() { try { // 上报心跳并更新客户端信息 //await redis.StoreClientInfoAsync(systemInfo); await redis.UpdateClientHeartbeatAsync(systemInfo.MachineId); } catch (Exception ex) { // 处理网络异常等 Debug.WriteLine($"心跳上报失败: {ex.Message}"); } } private void StartProgressTimer() { lock (_timerLock) { if (!_isTimerRunning) { _progressTimer.Start(); _heartbeatTimer.Start(); _isTimerRunning = true; } } } private void StopProgressTimer() { lock (_timerLock) { if (_isTimerRunning) { _progressTimer.Stop(); _heartbeatTimer.Stop(); _isTimerRunning = false; } } } #endregion #region 上传执行 //多文件上传 private async Task UploadFile() { var openFileDialog = new OpenFileDialog { Multiselect = true, Filter = "All Files (*.*)|*.*" }; if (openFileDialog.ShowDialog() == true) { // 过滤掉已经存在的文件 var newFiles = openFileDialog.FileNames .Where(filePath => !_sendViewModel.UpLoadItems.Any(item => item.Value5.Equals(filePath, StringComparison.OrdinalIgnoreCase))) .ToList(); if (newFiles.Count == 0) { MessageBox.Show("没有新文件需要上传或文件已在上传队列中"); return; } //判断是否存在上传路径 string bucketName = GetCurrentBucket(); if (string.IsNullOrEmpty(bucketName)) { MessageBox.Show("请选择上传路径"); return; } foreach (string filePath in newFiles) { var fix = GetCurrentPrefix(); //获取当前所在的文件夹路径,除桶之外的 string str = System.IO.Path.GetFileName(filePath); if (!string.IsNullOrEmpty(fix)) { str = fix + System.IO.Path.GetFileName(filePath); } var ut = CreateUploadItem(filePath, str); _sendViewModel.UpLoadItems.Add(ut); } _uploadCount = _sendViewModel.UpLoadItems.Count; //_uploadCount = _sendViewModel.UpLoadItems.Count; _completeCount = _sendViewModel.UpLoadItems.Where(r => r.Value3 == "已完成").Count(); _sendViewModel.FileCount = _completeCount + "/" + _uploadCount; UpdateFileCounts(); MessageBox.Show("传输列表中可查看进度"); // 如果没有上传任务在运行,则启动上传 if (!_isUploading) { await ProcessUploadTasks(); } } } //文件夹上传 private async Task UploadFile1() { var dialog = new CommonOpenFileDialog { IsFolderPicker = true, Title = "请选择上传文件" }; if (dialog.ShowDialog() == CommonFileDialogResult.Ok) { string folderPath = dialog.FileName; string folderName = new DirectoryInfo(folderPath).Name; // 获取选中的文件夹名称 var files = Directory.GetFiles(folderPath, "*.*", SearchOption.AllDirectories) .Where(filePath => !_sendViewModel.UpLoadItems.Any(item => item.Value5.Equals(filePath, StringComparison.OrdinalIgnoreCase))) .ToList(); if (files.Count == 0) { MessageBox.Show("没有新文件需要上传或文件已在上传队列中"); return; } //判断是否存在上传路径 string bucketName = GetCurrentBucket(); if (string.IsNullOrEmpty(bucketName)) { MessageBox.Show("请选择上传路径"); return; } foreach (string filePath in files) { var fix = GetCurrentPrefix(); //获取当前所在的文件夹路径,除桶之外的 string relativePath = folderName + '/' + filePath.Substring(folderPath.Length + 1).Replace('\\', '/'); if (!string.IsNullOrEmpty(fix)) { relativePath = fix + folderName + '/' + filePath.Substring(folderPath.Length + 1).Replace('\\', '/'); } var ut = CreateUploadItem(filePath, relativePath); _sendViewModel.UpLoadItems.Add(ut); } _uploadCount = _sendViewModel.UpLoadItems.Count; _completeCount = _sendViewModel.UpLoadItems.Where(r => r.Value3 == "已完成").Count(); _sendViewModel.FileCount = _completeCount + "/" + _uploadCount; UpdateFileCounts(); MessageBox.Show("传输列表中可查看进度"); // 如果没有上传任务在运行,则启动上传 if (!_isUploading) { await ProcessUploadTasks(); } } } //创建上传列表 private UpLoadItems CreateUploadItem(string filePath, string objectName) { FileInfo fileInfo = new FileInfo(filePath); string sizeText = fileInfo.Length < 1024 * 1024 ? $"{Math.Ceiling((decimal)fileInfo.Length / 1024)}KB" : $"{Math.Ceiling((decimal)fileInfo.Length / (1024 * 1024))}MB"; //写入数据库 FUpload fp = new FUpload(); fp.Id = Guid.NewGuid().ToString(); fp.FileName = objectName; fp.FileSize = fileInfo.Length; fp.FilePath = filePath; fp.FileType = fileInfo.Extension; fp.CreateTime = DateTime.Now; fp.IsComplete = false; fp.FileSizeText = sizeText; if (_uploadService.AddFile(fp)) { return new UpLoadItems { Value = System.IO.Path.GetFileName(filePath), Value3 = "等待上传", Value4 = fp.Id, //唯一标识,与数据库一致 Value5 = filePath, //文件名称 Value6 = objectName, //文件地址 Double1 = fileInfo.Length, Double2 = 0.0, Bool1 = false, Value1 = $"0{(fileInfo.Length < 1024 * 1024 ? "KB" : "MB")}/{sizeText}" }; } else { throw new Exception("文件载入失败"); } } //创建上传任务 private async Task ProcessUploadTasks() { _isUploading = true; StartProgressTimer(); // 收集系统信息 //var systemInfo = SystemInfoCollector.Collect(); try { // 存储到Redis await redis.StoreClientInfoAsync(systemInfo); while (true) { //获取所有未完成的上传项 var pendingItems = _sendViewModel.UpLoadItems .Where(item => !item.Bool1) .ToList(); if (!pendingItems.Any()) break; // 没有待上传文件,退出循环 //创建并运行上传任务 _currentUploadTask = Task.WhenAll(pendingItems.Select(async item => { await _semaphore.WaitAsync(_uploadCancellation.Token); try { await UploadFileToMinIOWithProgress(item); } finally { _semaphore.Release(); } })); await _currentUploadTask; } } catch (Exception ex) { //上传被取消 await redis.DeleteClientAsync(systemInfo.MachineId); } finally { await Task.Delay(2000); StopProgressTimer(); // 删除redis信息 await redis.DeleteClientAsync(systemInfo.MachineId); _isUploading = false; _sendViewModel.UpLoadItems.Clear(); //加载上传完成列表 GetCompletedFiles(); if (MySetting.Instance.IsOn && !_sendViewModel.UpLoadItems.Any(item => !item.Bool1)) { Shutdown(); } } } #region #endregion //上传具体执行 private async Task UploadFileToMinIOWithProgress(UpLoadItems ut) { ut.Bool1 = true; var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("global.json", optional: false, reloadOnChange: true); // 构建配置 var config = builder.Build(); // 获取滑块设置的速度限制(转换为字节 / 秒) // 查询所有客户端 var allClients = await redis.GetAllClientsAsync(); var num=allClients==null?1:allClients.Distinct().Count(); var speedLimit = 10 * 1024 * 1024 / num; //var speedLimit = Convert.ToInt64(config["Minio:limitspeed"]); var handler = new MinIOThrottledHandler(speedLimit, new HttpClientHandler { // 保持 MinIO 必需的 SSL 配置 ServerCertificateCustomValidationCallback = (msg, cert, chain, errors) => true }); try { // 从配置获取MinIO设置更安全 IMinioClient client = new MinioClient() .WithEndpoint(config["Minio:Endpoint"]) .WithCredentials(config["Minio:AccessKey"], config["Minio:SecretKey"]) .WithHttpClient(new HttpClient(handler) { Timeout = Timeout.InfiniteTimeSpan }) .Build(); string bucketName = GetCurrentBucket(); // 确保桶存在 var beArgs = new BucketExistsArgs().WithBucket(bucketName); bool found = await client.BucketExistsAsync(beArgs).ConfigureAwait(false); if (!found) { var mbArgs = new MakeBucketArgs().WithBucket(bucketName); await client.MakeBucketAsync(mbArgs).ConfigureAwait(false); } // 关键修改:添加完成信号等待 var completionSource = new TaskCompletionSource(); var progress = new Progress(progressReport => { Application.Current.Dispatcher.Invoke(() => { ut.Int1 = progressReport.Percentage; long trans = progressReport.TotalBytesTransferred; ut.Double2 = trans; int slashIndex = ut.Value1.IndexOf('/'); string sizePart = ut.Value1.Substring(slashIndex); string transferredPart = trans < 1024 * 1024 ? $"{Math.Ceiling((decimal)trans / 1024)}KB" : $"{Math.Ceiling((decimal)trans / (1024 * 1024))}MB"; ut.Value1 = $"{transferredPart}{sizePart}"; if (progressReport.Percentage == 100) { ut.Value7 = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); string id = ut.Value4.ToString(); _uploadService.UpdateFileComplete(id); _sendViewModel.CompleteItems.Add(ut); ut.Value3 = "已完成"; _sendViewModel.UpdateUploadingItems(); _completeCount++; _sendViewModel.FileCount = _completeCount + "/" + _uploadCount; UpdateFileCounts(); // 刷新当前目录 _explorerBehavior?.RefreshMinIOPath(CurrentMinIOPath); } else { ut.Value3 = "上传中..."; } if (progressReport.Percentage == 100) { // 延迟500ms确保MinIO完成处理 Task.Delay(500).ContinueWith(_ => { completionSource.TrySetResult(true); }); } }); }); // 对对象名称进行URL编码 //string objectName = WebUtility.UrlEncode(ut.Value6); var putObjectArgs = new PutObjectArgs() .WithBucket(bucketName) .WithObject(ut.Value6) .WithFileName(ut.Value5) .WithProgress(progress) .WithObjectSize(5*1024*1024); var uploadTask = client.PutObjectAsync(putObjectArgs); await Task.WhenAny(uploadTask, completionSource.Task); //await UploadVerifier.VerifyAsync(client, bucketName, objectName); if (uploadTask.IsFaulted) throw uploadTask.Exception; } catch (Exception ex) { Application.Current.Dispatcher.Invoke(() => { ut.Value3 = $"上传失败: {ex.Message}"; }); } } #endregion #region 其他 //关机 private void Shutdown() { try { Process.Start("shutdown", "/s /t 0"); } catch (Win32Exception ex) { MessageBox.Show($"关机失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } //更新上传进行中及上传完成列表 private void UpdateFileCounts() { _sendViewModel.UploadingCount = _sendViewModel.UploadingItems.Count; _sendViewModel.CompleteCount = _sendViewModel.CompleteItems.Count; } //加载初始完成文件 public void GetCompletedFiles() { var files = _uploadService.GetAllFiles().Where(r => r.IsComplete == true).ToList(); ObservableCollection up = new ObservableCollection(); foreach (var file in files) { UpLoadItems upLoadItems = new UpLoadItems(); upLoadItems.Value = file.FileName; upLoadItems.Value4 = file.Id; upLoadItems.Value5 = file.FilePath; upLoadItems.Value1 = file.FileSizeText; upLoadItems.Value6 = file.FileName; upLoadItems.Value7 = file.CompleteTime.ToString("yyyy-MM-dd HH:mm:ss"); up.Add(upLoadItems); } _sendViewModel.CompleteItems.Clear(); _sendViewModel.CompleteItems.AddRange(up); regionCount = up.Count; _sendViewModel.CompleteCount = regionCount; } #endregion #endregion #region 文件下载 private DownFolderSelect _downFolderSelect = new DownFolderSelect(); public DownFolderSelect DownFolderSelect { get => _downFolderSelect; set { _downFolderSelect = value; RaisePropertyChanged(); } } private async Task DoDownloadCommand() { // 当为文件时,处理 var tempSelectedItems = new ObservableCollection(SelectedItems); if (tempSelectedItems.Count > 0) { if (string.IsNullOrEmpty(DownFolderSelect.DownFolder)) { // 如果是空,则把同步目录拿过来使用 DownFolderSelect.DownFolder = ViewModelLocator.SyncViewModel.SyncDir; } //下载目录 bool r = await MessageProxy.Presenter.Show(DownFolderSelect, x => true, "下载目录选择", x => { x.Width = 600; x.Height = 450; //x.Padding = new System.Windows.Thickness(10); //x.HorizontalAlignment = System.Windows.HorizontalAlignment.Center; //x.VerticalAlignment = System.Windows.VerticalAlignment.Center; }, ObjectContentDialog.ClearKey); if (r) { var downDir = DownFolderSelect.DownFolder; if (DownFolderSelect.IsSelect) { // todo 持久化配置 } SelectedItems.Clear(); _explorerBehavior.RefreshMinIOPath(CurrentMinIOPath); Task.Run(() => AddTaskToDownLoad(tempSelectedItems, downDir)); } } else { await MessageProxy.Messager.ShowResult("至少选择一项"); } } private void AddTaskToDownLoad(ObservableCollection tempSelectedItems, string downDir) { foreach (var item in tempSelectedItems) { if (item is MinIOFileModel file) { var temp = (MinIOFileInfo)file.Model; var objectKey = temp.FullName.Replace(temp.BucketName + "/", ""); ViewModelLocator.DownViewModel.AddTask(temp.BucketName, objectKey, temp.Size, downDir); } else if (item is MinIODirectoryModel dir) { var temp = (MinIODirectoryInfo)dir.Model; //Console.WriteLine($"bucket: {temp.BucketName} objectKey {temp.FullName}"); var objectKey = temp.FullName.Replace(temp.BucketName + "/", ""); ViewModelLocator.DownViewModel.AddDirTask(temp.BucketName, objectKey, downDir); } MessageProxy.Snacker.Show("已加入下载队列"); } } #endregion #region 文件列表 private string _currentMinIOPath; /// 说明CurrentMinIOPath public string CurrentMinIOPath { get { return _currentMinIOPath; } set { // 确保路径格式正确 var newPath = value; if (!string.IsNullOrEmpty(newPath) && !newPath.EndsWith("/") && newPath.Contains("/") && !newPath.EndsWith("/")) { newPath += "/"; } if (_currentMinIOPath != newPath) { _currentMinIOPath = newPath; RaisePropertyChanged("CurrentMinIOPath"); //// 强制刷新导航栏 //if (_explorerBehavior != null) //{ // _explorerBehavior.RefreshMinIOPath(newPath); //} } } } public ObservableCollection Items { get; } = new ObservableCollection(); //Explorer创建新建文件夹并可编辑 private async Task CreateNewFolderAsync() { try { // 创建新的文件夹模型 var newFolder = new MinIODirectoryModel( new MinIODirectoryInfo(GetCurrentBucket(), "新建文件夹/", false, DateTime.Now)) { IsRenaming = false // 设置为可编辑状态 }; // 通知行为类开始编辑 _explorerBehavior?.BeginEditNewFolder(newFolder); await Task.Delay(100); // 等待UI更新 } catch (Exception ex) { MessageBox.Show($"创建文件夹失败: {ex.Message}"); } } //minio创建新建文件 public async Task CreateMinIOFolder(string folderName) { var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("global.json", optional: false, reloadOnChange: true); // 构建配置 var config = builder.Build(); // 从配置获取MinIO设置更安全 IMinioClient client = new MinioClient() .WithEndpoint(config["Minio:Endpoint"]) .WithCredentials(config["Minio:AccessKey"], config["Minio:SecretKey"]) .Build(); if (string.IsNullOrWhiteSpace(folderName)) return; if (!folderName.EndsWith("/")) folderName += "/"; try { var bucketName = GetCurrentBucket(); var prefix = GetCurrentPrefix(); var beArgs = new BucketExistsArgs().WithBucket(bucketName); bool found = await client.BucketExistsAsync(beArgs); if (!found) { var mbArgs = new MakeBucketArgs().WithBucket(bucketName); await client.MakeBucketAsync(mbArgs); } using (var emptyStream = new MemoryStream(Encoding.UTF8.GetBytes(" "))) { await client.PutObjectAsync( new PutObjectArgs() .WithBucket(bucketName) .WithObject(prefix + folderName) .WithStreamData(emptyStream) .WithObjectSize(1) // 必须设置为0 .WithContentType("application/x-directory")); } // 刷新当前目录 _explorerBehavior?.RefreshMinIOPath(CurrentMinIOPath); } catch (Exception ex) { MessageBox.Show($"在MinIO中创建文件夹失败: {ex.Message}"); } } //获取当前路径下的桶 private string GetCurrentBucket() { if (string.IsNullOrEmpty(CurrentMinIOPath)) return string.Empty; return CurrentMinIOPath.Split('/')[0]; } //获取桶下的文件路径 private string GetCurrentPrefix() { if (string.IsNullOrEmpty(CurrentMinIOPath) || CurrentMinIOPath.IndexOf('/') < 0) return string.Empty; return CurrentMinIOPath.Substring(CurrentMinIOPath.IndexOf('/') + 1); } #endregion #region 删除文件夹 private ObservableCollection _selectedItems = new ObservableCollection(); public ObservableCollection SelectedItems { get { return _selectedItems; } set { _selectedItems = value; RaisePropertyChanged(); } } private ICommand _selectItemsCommand; public ICommand SelectItemsCommand => _selectItemsCommand ?? (_selectItemsCommand = new RelayCommand(SelectItem)); public ICommand SelectDirCommand { get; set; } private void SelectItem(SystemInfoModel item) { if (item == null) return; if (SelectedItems.Contains(item)) { SelectedItems.Remove(item); } else { SelectedItems.Add(item); } } private async Task DeleteSelectedItemsAsync() { try { var selectedItems = _selectedItems; if (selectedItems == null || !selectedItems.Any()) { await MessageProxy.Messager.ShowResult("请先选择要删除的项目"); return; } var message = $"确定要删除选中的 {selectedItems.Count} 个项目吗?"; var result = MessageBox.Show(message, "确认删除", MessageBoxButton.YesNo); if (result == MessageBoxResult.Yes) { await DeleteMinIOItems(selectedItems); MessageBox.Show($"已成功删除 {selectedItems.Count} 个项目"); _explorerBehavior.RefreshMinIOPath(CurrentMinIOPath); SelectedItems.Clear(); } } catch (Exception ex) { MessageBox.Show($"删除失败: {ex.Message}"); } } private async Task DeleteMinIOItems(IEnumerable items) { var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("global.json", optional: false, reloadOnChange: true); var config = builder.Build(); using var client = new MinioClient() .WithEndpoint(config["Minio:Endpoint"]) .WithCredentials(config["Minio:AccessKey"], config["Minio:SecretKey"]) .Build(); // 按桶分组 var itemsByBucket = items.GroupBy(item => { if (item is MinIOFileModel file) return ((MinIOFileInfo)file.Model).BucketName; if (item is MinIODirectoryModel dir) return ((MinIODirectoryInfo)dir.Model).BucketName; return null; }); foreach (var bucketGroup in itemsByBucket.Where(g => g.Key != null)) { var bucketName = bucketGroup.Key; var objectsToDelete = new List(); foreach (var item in bucketGroup) { if (item is MinIOFileModel fileModel) { var fileInfo = (MinIOFileInfo)fileModel.Model; objectsToDelete.Add(fileInfo.FullName.Substring(fileInfo.BucketName.Length + 1)); } else if (item is MinIODirectoryModel dirModel) { var dirInfo = (MinIODirectoryInfo)dirModel.Model; var folderPath = dirInfo.FullName.Substring(dirInfo.BucketName.Length + 1); // 确保文件夹路径以斜杠结尾 if (!folderPath.EndsWith("/")) folderPath += "/"; // 获取文件夹下所有对象 var listArgs = new ListObjectsArgs() .WithBucket(bucketName) .WithPrefix(folderPath) .WithRecursive(true); await foreach (var obj in client.ListObjectsEnumAsync(listArgs)) { objectsToDelete.Add(obj.Key); } // 添加文件夹标记本身 objectsToDelete.Add(folderPath); } } // 批量删除 if (objectsToDelete.Any()) { await client.RemoveObjectsAsync( new RemoveObjectsArgs() .WithBucket(bucketName) .WithObjects(objectsToDelete)); } } } #endregion } internal class DataFileViewModel : ObservableSourceViewModel { protected override void Init() { base.Init(); } } }