using System; using System.Collections.ObjectModel; using System.ComponentModel; using System.IO; using System.Net.Http; using System.Security.AccessControl; using System.Windows; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Threading; using HeBianGu.Base.WpfBase; using HeBianGu.General.WpfControlLib; using HeBianGu.Service.Mvc; using Hopetry.Provider; using Hopetry.Provider.Behaviors; using Hopetry.Services; using Hopetry.ViewModel.Send; using Microsoft.Extensions.Configuration; using Minio.DataModel.Args; using Minio.DataModel; using Minio; using Newtonsoft.Json.Linq; using System.Diagnostics; using System.Timers; namespace HeBianGu.App.Disk { [ViewModel("Send")] internal class SendViewModel : MvcViewModelBase { private LinkAction _uploadingAction; //正在上传 private LinkAction _completeAction; //上传完成 public LinkAction DownLinkAction; public LinkAction _waituploadAction; private readonly FileUploadService _uploadService; public ICommand WaitUpLoadCommand { get; } public ICommand DeleteWaitUpLoadCommand { get; } public bool _isUploading = false; public string CurrentMinIOPath; public SendViewModel() { _uploadService = new FileUploadService(); // 初始化Timer _progressTimer = new System.Timers.Timer(1000); _progressTimer.Elapsed += UpdateProgress; _heartbeatTimer = new System.Timers.Timer(30_000); //_heartbeatTimer.Elapsed += async (s, e) => await SendHeartbeatAsync(); DeleteWaitUpLoadCommand = new AsyncRelayCommand(async () => await DeleteFile()); WaitUpLoadCommand = new AsyncRelayCommand(async () => await UploadWaitItems()); systemInfo = SystemInfoCollector.Collect(); //加载待上传列表 GetWaitUploadItems(); //加载上传完成列表 GetCompletedFiles(); } protected override void Init() { ITransitionWipe wipe = new CircleWipe(); DownLinkAction = new LinkAction() { Action = "Down", Controller = "Send", DisplayName = "下载 0", Logo = "\xe891", TransitionWipe = wipe }; LinkActions.Add(DownLinkAction); // 正在上传 _uploadingAction = new LinkAction() { Action = "UpLoad", Controller = "Send", DisplayName = "正在上传", Logo = "\xe6f3", TransitionWipe = wipe }; LinkActions.Add(_uploadingAction); _completeAction = new LinkAction() { Action = "CompleteUpload", Controller = "Send", DisplayName = "上传完成", Logo = "\xe613", TransitionWipe = wipe }; LinkActions.Add(_completeAction); _waituploadAction = new LinkAction() { Action = "WaitUpload", Controller = "Send", DisplayName = "待上传", Logo = "\xe613", TransitionWipe = wipe }; LinkActions.Add(_waituploadAction); Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() => { SelectLink = LinkActions[0]; })); UpLoadItems.CollectionChanged += (s, e) => { UpdateUploadingItems(); }; } protected override void Loaded(string args) { } #region 文件上传 #region 参数 //上传文件总数 private ObservableCollection _upLoadItems = new ObservableCollection(); /// 说明 public ObservableCollection UpLoadItems { get { return _upLoadItems; } set { _upLoadItems = value; //UpdateUploadingItems(); RaisePropertyChanged("UpLoadItems"); } } //已上传完成文件 private ObservableCollection _completeItems = new ObservableCollection(); public ObservableCollection CompleteItems { get { return _completeItems; } private set { _completeItems = value; RaisePropertyChanged("CompleteItems"); } } //待上传文件 private ObservableCollection _waitUpLoadItems = new ObservableCollection(); /// 说明 public ObservableCollection WaitUpLoadItems { get { return _waitUpLoadItems; } set { _waitUpLoadItems = value; RaisePropertyChanged("WaitUpLoadItems"); } } private bool? _isAllSelected; public bool? IsAllSelected { get => _isAllSelected; set { _isAllSelected = value; // 全选/取消全选逻辑 if (value.HasValue) { foreach (var item in WaitUpLoadItems) { item.IsSelect = value.Value; } } } } // 只读属性,返回过滤后的集合(动态计算) private ObservableCollection _uploadingItems = new ObservableCollection(); public ObservableCollection UploadingItems { get { return _uploadingItems; } private set { _uploadingItems = value; RaisePropertyChanged("UploadingItems"); } } // 在 UpLoadItems 变化时更新 UploadingItems public void UpdateUploadingItems() { UploadingItems = new ObservableCollection( UpLoadItems?.Where(item => item.Value3 != "已完成") ?? Enumerable.Empty() ); } //文件上传进度 private double _progress; public double Progress { get { return _progress; } set { if (_progress != value) { _progress = value; RaisePropertyChanged("Progress"); } } } //上传文件个数 private string _filecount; public string FileCount { get { return _filecount; } set { if (_filecount != value) { _filecount = value; RaisePropertyChanged("FileCount"); } } } //上传完成文件个数 private int _uploadingcount; public int UploadingCount { get { return _uploadingcount; } set { if (_uploadingcount != value) { _uploadingcount = value; _uploadingAction.FullName = "(" + _uploadingcount + ")"; RaisePropertyChanged("UploadingCount"); } } } //上传文件个数 private int _completecount; public int CompleteCount { get { return _completecount; } set { if (_completecount != value) { _completecount = value; _completeAction.FullName = "(" + _completecount + ")"; RaisePropertyChanged("CompleteCount"); } } } //等待上传文件个数 private int _waitcount; public int WaitCount { get { return _waitcount; } set { if (_waitcount != value) { _waitcount = value; _waituploadAction.FullName = "(" + _waitcount + ")"; RaisePropertyChanged("WaitCount"); } } } #endregion #region PropertyChanged public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } #endregion #endregion #region 删除等待上传文件 private async Task DeleteFile() { try { var items = WaitUpLoadItems.Where(r => r.IsSelect == true).ToList(); if (items == null || !items.Any()) { await MessageProxy.Messager.ShowResult("请先选择要删除的项目"); return; } var message = $"确定要删除选中的 {items.Count} 个项目吗?"; var result = MessageBox.Show(message, "确认删除", MessageBoxButton.YesNo); if (result == MessageBoxResult.Yes) { List list = new List(); list=items.Select(r => r.Value4).ToList(); var flag= _uploadService.DeleteFiles(list); WaitUpLoadItems.RemoveAll(r=>r.IsSelect==true); WaitCount= WaitUpLoadItems.Count; if (flag == true) { MessageBox.Show($"已成功删除 {items.Count} 个项目"); IsAllSelected = false; } else { MessageBox.Show($"删除失败"); } } } catch (Exception ex) { MessageBox.Show($"删除失败: {ex.Message}"); } } #endregion #region 上传待上传文件 public async Task UploadWaitItems() { var items = WaitUpLoadItems.Where(r => r.IsSelect == true).ToList(); try { foreach (var item in items) { FileInfo fileInfo = new FileInfo(item.Value5); string sizeText = fileInfo.Length < 1024 * 1024 ? $"{Math.Ceiling((decimal)fileInfo.Length / 1024)}KB" : $"{Math.Ceiling((decimal)fileInfo.Length / (1024 * 1024))}MB"; UpLoadItems ut = new UpLoadItems(); ut.Value = item.Value; ut.Value3 = "等待上传"; ut.Value4 = item.Value4; //唯一标识,与数据库一致 ut.Value5 = item.Value5; //文件名称 ut.Value6 = item.Value6; //文件地址 ut.Value9 = item.Value9; ut.Double1 = fileInfo.Length; ut.Double2 = 0.0; ut.Bool1 = false; ut.Value1 = $"0{(fileInfo.Length < 1024 * 1024 ? "KB" : "MB")}/{sizeText}"; UpLoadItems.Add(ut); } _uploadCount = UpLoadItems.Count; _completeCount = UpLoadItems.Where(r => r.Value3 == "已完成").Count(); FileCount = _completeCount + "/" + _uploadCount; UploadingCount = UploadingItems.Count; CompleteCount = CompleteItems.Count; MessageBox.Show("正在上传列表中可查看进度"); WaitUpLoadItems.RemoveAll(r => r.IsSelect == true); IsAllSelected = false; WaitCount = WaitUpLoadItems.Count; // 如果没有上传任务在运行,则启动上传 if (!_isUploading) { try { await ProcessUploadTasks(); } catch { MessageBox.Show("上传失败"); } } } catch (Exception ex) { MessageBox.Show("文件丢失"); } } #endregion #region 获取等待上传列表 public void GetWaitUploadItems() { var waitlist = _uploadService.GetAllFiles().Where(r => r.IsComplete == false).ToList(); ObservableCollection up = new ObservableCollection(); if (_isUploading == false) { foreach (var file in waitlist) { 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.CreateTime.ToString("yyyy-MM-dd HH:mm:ss"); upLoadItems.Value9 = file.BucketName; upLoadItems.Bool1 = false; up.Add(upLoadItems); } WaitUpLoadItems.Clear(); WaitUpLoadItems.AddRange(up); WaitCount = up.Count; } } #endregion #region 加载上传完成列表 //加载初始完成文件 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"); upLoadItems.Value9 = file.BucketName; up.Add(upLoadItems); } CompleteItems.Clear(); CompleteItems.AddRange(up); regionCount = up.Count; CompleteCount = regionCount; } #endregion #region 上传 //private SendViewModel _sendViewModel; private SemaphoreSlim _semaphore = new SemaphoreSlim(5); private System.Timers.Timer _progressTimer; private System.Timers.Timer _heartbeatTimer; private bool _isTimerRunning = false; private object _timerLock = new object(); private Task _currentUploadTask = null; // 新增:当前上传任务 private CancellationTokenSource _uploadCancellation = new CancellationTokenSource(); public int _uploadCount = 0; private int _completeCount = 0; private int regionCount = 0; private ExplorerMinIOBehavior _explorerBehavior; private Hopetry.Provider.SystemInfo systemInfo; // 配置Redis连接 //private RedisService redis = new RedisService("175.27.168.120:6050,password=HopetryRedis1406,connectRetry=3"); #region 更新总进度条 每隔1s更新一次 private void UpdateProgress(object sender, ElapsedEventArgs e) { if (UpLoadItems.Count == 0) return; double currentBytes = UpLoadItems.Sum(r => r.Double2); double totalBytes = UpLoadItems.Sum(r => r.Double1); double progress = Math.Round((currentBytes / totalBytes) * 100, 2); Application.Current.Dispatcher.Invoke(() => { 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 //创建上传任务 public async Task ProcessUploadTasks() { _isUploading = true; StartProgressTimer(); // 收集系统信息 //var systemInfo = SystemInfoCollector.Collect(); try { // 存储到Redis //await redis.StoreClientInfoAsync(systemInfo); while (true) { //获取所有未完成的上传项 var pendingItems = 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; UpLoadItems.Clear(); //加载上传完成列表 GetCompletedFiles(); if (MySetting.Instance.IsOn && !UpLoadItems.Any(item => !item.Bool1)) { Shutdown(); } } } //上传具体执行 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/5; var speedLimit = 100 * 1024 * 1024; //var speedLimit = Convert.ToInt64(config["Minio:limitspeed"]); var handler = new MinIOThrottledHandler(speedLimit, new HttpClientHandler { // 保持 MinIO 必需的 SSL 配置 ServerCertificateCustomValidationCallback = (msg, cert, chain, errors) => true }); //var handler = // 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() { Timeout = TimeSpan.FromMinutes(30) }) .Build(); //string bucketName = GetCurrentBucket(); string bucketName = ut.Value9; if (string.IsNullOrEmpty(bucketName)) { throw new Exception("桶不能为空"); } // 确保桶存在 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); CompleteItems.Add(ut); ut.Value3 = "已完成"; UpdateUploadingItems(); _completeCount++; 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}"; }); } } //获取当前路径下的桶 private string GetCurrentBucket() { if (string.IsNullOrEmpty(CurrentMinIOPath)) return string.Empty; return CurrentMinIOPath.Split('/')[0]; } //更新上传进行中及上传完成列表 private void UpdateFileCounts() { UploadingCount = UploadingItems.Count; CompleteCount = CompleteItems.Count; } //关机 private void Shutdown() { try { Process.Start("shutdown", "/s /t 0"); } catch (Win32Exception ex) { MessageBox.Show($"关机失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } #endregion } }