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; using Hopetry.Services; 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; // 分页相关字段 private int _waitUploadCurrentPage = 1; private int _completedCurrentPage = 1; private const int PAGE_SIZE = 50; // 上传完成事件 public event Action FileUploadCompleted; 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(1); // 从第一页开始加载 //加载上传完成列表 GetCompletedFiles(1); // 从第一页开始加载 //new Thread(ProcessUploadTasks) { IsBackground = true }.Start(); Task.Run(() => ProcessUploadTasks()); // 启动文件上传线程 } 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 decimal _progress; public decimal Progress { get { return _progress; } set { if (_progress != value) { _progress = value; RaisePropertyChanged(); } } } //上传文件个数 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 long _lastMemoryUsage = GC.GetTotalMemory(false); public long MemoryUsage { get { return GC.GetTotalMemory(false); } } public double MemoryUsageMB { get { return Math.Round(GC.GetTotalMemory(false) / 1024.0 / 1024.0, 2); } } //等待上传文件个数 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 = (CompleteItems.Count) + "/" + (UpLoadItems.Count + CompleteItems.Count); 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(int pageNumber = 1) { var waitlist = _uploadService.GetIncompleteFiles(pageNumber, PAGE_SIZE); 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 = _uploadService.GetIncompleteFileCount(); // 从数据库获取总数 } } #endregion #region 加载上传完成列表 //加载初始完成文件 public void GetCompletedFiles(int pageNumber = 1) { var files = _uploadService.GetCompleteFiles(pageNumber, PAGE_SIZE); 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 = _uploadService.GetCompleteFileCount(); // 从数据库获取总数 CompleteCount = regionCount; } // 加载更多待上传文件 public void LoadMoreWaitUploadItems() { _waitUploadCurrentPage++; var waitlist = _uploadService.GetIncompleteFiles(_waitUploadCurrentPage, PAGE_SIZE); if (waitlist.Count > 0) { 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; WaitUpLoadItems.Add(upLoadItems); } WaitCount = _uploadService.GetIncompleteFileCount(); } } // 加载更多已完成文件 public void LoadMoreCompletedItems() { _completedCurrentPage++; var files = _uploadService.GetCompleteFiles(_completedCurrentPage, PAGE_SIZE); if (files.Count > 0) { 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; CompleteItems.Add(upLoadItems); } regionCount = _uploadService.GetCompleteFileCount(); CompleteCount = regionCount; } } #endregion #region 上传 //private SendViewModel _sendViewModel; private SemaphoreSlim _semaphore = new SemaphoreSlim(3); private System.Timers.Timer _progressTimer; private System.Timers.Timer _heartbeatTimer; private bool _isTimerRunning = false; private object _timerLock = new object(); private Task _currentUploadTask = null; // 新增:当前上传任务 public CancellationTokenSource _uploadCancellation = new(); public int _uploadCount = 0; public 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 DateTime _lastProgressUpdate = DateTime.MinValue; private void UpdateProgress(object sender, ElapsedEventArgs e) { if (UpLoadItems.Count == 0) return; var now = DateTime.Now; /*// 限制进度更新频率 if ((now - _lastProgressUpdate).TotalMilliseconds < 500) // 至少500ms更新一次 { return; } double currentBytes = UpLoadItems.Sum(r => r.Double2); double totalBytes = UpLoadItems.Sum(r => r.Double1);*/ FileCount = (CompleteItems.Count) + "/" + (UpLoadItems.Count + CompleteItems.Count); // 避免除零错误 var progress = UpLoadItems.Count > 0 ? Math.Round((decimal)(CompleteItems.Count * 100/ (UpLoadItems.Count + CompleteItems.Count)) , 2) : 0; Application.Current.Dispatcher.Invoke(() => { Progress = progress; _lastProgressUpdate = now; }); } //上传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() { if (_isUploading) return; // 避免重复启动 _isUploading = true; StartProgressTimer(); try { // 这里只控制了获取下载令牌的循环 while (!_uploadCancellation.Token.IsCancellationRequested) { // 在后台线程获取待处理项 var pendingItems = UpLoadItems.Where(r => !r.Bool1).ToList(); if (!pendingItems.Any()) await Task.Delay(500); // 使用异步延时替代同步休眠 // 分批处理,避免一次性启动过多任务 const int batchSize = 10; for (int i = 0; i < pendingItems.Count; i += batchSize) { var batch = pendingItems.Skip(i).Take(batchSize).ToList(); //创建并运行上传任务 var batchTasks = batch.Select(async item => { // 使用带超时的信号量等待 if (await _semaphore.WaitAsync(-1, _uploadCancellation.Token)) // 30秒超时 { try { await UploadFileToMinIOWithProgress(item); } finally { _semaphore.Release(); } } else { // 信号量获取超时,标记为失败 await Application.Current.Dispatcher.BeginInvoke(() => { item.Value3 = "上传超时"; _uploadService.UpdateFileComplete(item.Value4.ToString(), false); }); } }); await Task.WhenAll(batchTasks); // 批次间短暂延迟,避免过度占用资源 //await Task.Delay(10); } } } catch (OperationCanceledException) { // 正常取消操作 Console.WriteLine("上传任务被取消"); } catch (Exception ex) { Console.WriteLine($"上传任务异常: {ex.Message}"); // 上传被取消 } finally { await Task.Delay(1000); StopProgressTimer(); _isUploading = false; // 在UI线程上更新界面 await Application.Current.Dispatcher.BeginInvoke(() => { UpLoadItems.Clear(); //加载上传完成列表 GetCompletedFiles(1); // 重新加载第一页 if (MySetting.Instance.IsOn && !UpLoadItems.Any(item => !item.Bool1)) { Shutdown(); } // 触发上传完成事件,让网盘空间更新数据 FileUploadCompleted?.Invoke(CurrentMinIOPath); // 执行垃圾回收以释放内存 GC.Collect(); GC.WaitForPendingFinalizers(); }); } } //上传具体执行 private async Task UploadFileToMinIOWithProgress(UpLoadItems ut) { ut.Bool1 = true; try { 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 // }; // 从配置获取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); } // 限制UI更新频率 DateTime lastUiUpdate = DateTime.MinValue; var progress = new Progress(progressReport => { var now = DateTime.Now; // 限制UI更新频率,每200ms最多更新一次 if ((now - lastUiUpdate).TotalMilliseconds > 200) { Application.Current.Dispatcher.InvokeAsync(() => { 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}"; ut.Value3 = progressReport.Percentage == 100 ? "验证中..." : "上传中..."; lastUiUpdate = now; }); } }); // 使用安全上传助手进行文件上传 bool uploadSuccess = await UploadHelper.SafeUploadFileAsync( client, bucketName, ut.Value5, ut.Value6, progress); if (!uploadSuccess) { throw new Exception("文件上传失败"); } // 上传完成后进行验证 await UploadVerifier.VerifyAsync(client, bucketName, ut.Value6); // 验证成功后更新状态 ut.Value7 = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); string id = ut.Value4.ToString(); await _uploadService.UpdateFileCompleteAsync(id, true); // Application.Current.Dispatcher.Invoke // 在UI线程上更新界面 await Application.Current.Dispatcher.BeginInvoke(() => { // 从上传列表中移除已完成的项目 UpLoadItems.Remove(ut); // 添加到完成列表 CompleteItems.Add(ut); ut.Value3 = "已完成"; UpdateUploadingItems(); _completeCount++; FileCount = (CompleteItems.Count) + "/" + (UpLoadItems.Count + CompleteItems.Count); UpdateFileCounts(); // 刷新当前目录 //_explorerBehavior?.RefreshMinIOPath(CurrentMinIOPath); // 触发上传完成事件 FileUploadCompleted?.Invoke(CurrentMinIOPath); Progress = UpLoadItems.Count > 0 ? Math.Round((decimal)(CompleteItems.Count * 100/ (UpLoadItems.Count + CompleteItems.Count)) , 2) : 0; //Console.WriteLine($"当前上传进度:{Progress}%"); // 更新网盘空间页面状态 /*if (_explorerBehavior != null) { // 延迟刷新以确保MinIO有时间同步数据 Task.Delay(500).ContinueWith(_ => { Application.Current.Dispatcher.Invoke(() => { _explorerBehavior.RefreshMinIOPath(CurrentMinIOPath); }); }); }*/ }); } catch (Exception ex) { await Application.Current.Dispatcher.BeginInvoke(() => { ut.Value3 = $"上传失败: {ex.Message}"; }); try { await _uploadService.UpdateFileCompleteAsync(ut.Value4.ToString(), false); } catch (Exception dbEx) { Console.WriteLine($"数据库更新失败: {dbEx.Message}"); } Console.WriteLine($"文件 {ut.Value} 上传失败: {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; } public void AddUploadTask(UpLoadItems ut) { Application.Current.Dispatcher.BeginInvoke(() => { UpLoadItems.Add(ut); _uploadCount = UpLoadItems.Count; _completeCount = UpLoadItems.Where(r => r.Value3 == "已完成").Count(); FileCount = (CompleteItems.Count) + "/" + (UpLoadItems.Count + CompleteItems.Count); UpdateFileCounts(); }); } //关机 private void Shutdown() { try { Process.Start("shutdown", "/s /t 0"); } catch (Win32Exception ex) { MessageBox.Show($"关机失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } // 清理资源 public void Cleanup() { _progressTimer?.Stop(); _progressTimer?.Dispose(); _heartbeatTimer?.Stop(); _heartbeatTimer?.Dispose(); _semaphore?.Release(); _uploadCancellation?.Cancel(); _uploadCancellation?.Dispose(); // 清理集合以释放内存 UpLoadItems?.Clear(); CompleteItems?.Clear(); WaitUpLoadItems?.Clear(); } #endregion } }