using WorkCameraExport.Models; using WorkCameraExport.Services; namespace WorkCameraExport.Forms { /// /// 迁移窗体 - 历史数据迁移功能 /// public partial class MigrationForm : Form { private readonly ApiService _apiService; private readonly CosService _cosService; private CancellationTokenSource? _migrationCts; private bool _isMigrating; // 记录数据 private List _records = new(); private int _totalRecords; public MigrationForm(ApiService apiService) { InitializeComponent(); _apiService = apiService; _cosService = new CosService(); } /// /// 窗体加载事件 /// private void MigrationForm_Load(object sender, EventArgs e) { InitializeDefaults(); } /// /// 初始化默认值 /// private void InitializeDefaults() { // 设置默认日期范围(最近90天) dtpEndDate.Value = DateTime.Today; dtpStartDate.Value = DateTime.Today.AddDays(-90); // 设置默认状态为"未迁移" cboStatus.SelectedIndex = 1; // 未迁移 UpdateMigrateButtonState(); } #region 查询功能 /// /// 查询按钮点击事件 /// private async void btnQuery_Click(object sender, EventArgs e) { await DoQueryAsync(); } /// /// 执行查询 /// private async Task DoQueryAsync() { SetQueryControlsEnabled(false); lblStatusBar.Text = "正在查询..."; lblStatusBar.ForeColor = Color.Blue; dgvRecords.Rows.Clear(); _records.Clear(); _totalRecords = 0; try { var query = BuildQuery(); query.PageNum = 1; query.PageSize = 50; // 获取所有记录(分页获取) var allRecords = new List(); int pageNum = 1; while (true) { query.PageNum = pageNum; var (success, message, data) = await _apiService.GetMigrationListAsync(query); if (!success || data == null) { ShowError(message); return; } allRecords.AddRange(data.Result); _totalRecords = data.TotalNum; if (data.Result.Count < 50 || pageNum >= data.TotalPage) break; pageNum++; // 更新状态 lblStatusBar.Text = $"正在查询... 已获取 {allRecords.Count} 条"; } _records = allRecords; // 显示数据 foreach (var record in _records) { dgvRecords.Rows.Add( false, // 选择框 record.Id, record.RecordTime?.ToString("yyyy-MM-dd HH:mm") ?? "", record.DeptName, TruncateContent(record.Content, 40), record.ImageCount, GetStatusText(record.MigrationStatus) ); } // 更新统计信息 lblTotalRecords.Text = $"共 {_totalRecords} 条记录"; UpdateSelectedCount(); lblStatusBar.Text = $"查询完成,共 {_totalRecords} 条记录"; lblStatusBar.ForeColor = Color.Green; } catch (Exception ex) { ShowError($"查询异常: {ex.Message}"); } finally { SetQueryControlsEnabled(true); UpdateMigrateButtonState(); } } /// /// 构建查询参数 /// private MigrationQuery BuildQuery() { int? status = cboStatus.SelectedIndex switch { 1 => 0, // 未迁移 2 => 1, // 已迁移 3 => 2, // 迁移失败 _ => null // 全部 }; return new MigrationQuery { StartDate = dtpStartDate.Value.Date, EndDate = dtpEndDate.Value.Date.AddDays(1).AddSeconds(-1), Status = status }; } /// /// 获取状态文本 /// private static string GetStatusText(int status) { return status switch { 0 => "未迁移", 1 => "已迁移", 2 => "迁移失败", _ => "未知" }; } #endregion #region 选择功能 /// /// 全选/取消复选框变更事件 /// private void chkSelectAll_CheckedChanged(object sender, EventArgs e) { foreach (DataGridViewRow row in dgvRecords.Rows) { row.Cells[0].Value = chkSelectAll.Checked; } UpdateSelectedCount(); UpdateMigrateButtonState(); } /// /// 数据网格单元格点击事件 /// private void dgvRecords_CellContentClick(object sender, DataGridViewCellEventArgs e) { if (e.ColumnIndex == 0 && e.RowIndex >= 0) { // 延迟更新,等待复选框状态变更 BeginInvoke(new Action(() => { UpdateSelectedCount(); UpdateMigrateButtonState(); })); } } /// /// 更新已选数量 /// private void UpdateSelectedCount() { int selectedCount = GetSelectedRecordIds().Count; lblSelectedRecords.Text = $"已选 {selectedCount} 条"; } /// /// 获取选中的记录ID列表 /// private List GetSelectedRecordIds() { var selectedIds = new List(); foreach (DataGridViewRow row in dgvRecords.Rows) { if (row.Cells[0].Value is true) { if (int.TryParse(row.Cells[1].Value?.ToString(), out int id)) { selectedIds.Add(id); } } } return selectedIds; } /// /// 获取选中的记录 /// private List GetSelectedRecords() { var selectedIds = GetSelectedRecordIds(); return _records.Where(r => selectedIds.Contains(r.Id)).ToList(); } #endregion #region 迁移功能 /// /// 迁移按钮点击事件 /// private async void btnMigrate_Click(object sender, EventArgs e) { if (_isMigrating) { // 取消迁移 _migrationCts?.Cancel(); return; } var selectedRecords = GetSelectedRecords(); if (selectedRecords.Count == 0) { MessageBox.Show("请先选择要迁移的记录", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } var result = MessageBox.Show( $"确定要迁移选中的 {selectedRecords.Count} 条记录吗?\n\n迁移过程中请勿关闭程序。", "确认迁移", MessageBoxButtons.YesNo, MessageBoxIcon.Question); if (result != DialogResult.Yes) return; await DoMigrationAsync(selectedRecords); } /// /// 执行迁移 /// private async Task DoMigrationAsync(List records) { _isMigrating = true; _migrationCts = new CancellationTokenSource(); SetMigratingState(true); int successCount = 0; int failCount = 0; int totalRecords = records.Count; int processedRecords = 0; try { // 获取 COS 临时密钥 lblCurrentRecord.Text = "正在获取 COS 临时密钥..."; var (credSuccess, credMessage, credentials) = await _apiService.GetTempCredentialsAsync(); if (!credSuccess || credentials == null) { ShowError($"获取 COS 临时密钥失败: {credMessage}"); return; } // 初始化 COS 服务 _cosService.Initialize(credentials); // 逐条迁移记录 foreach (var record in records) { if (_migrationCts.Token.IsCancellationRequested) { lblStatusBar.Text = "迁移已取消"; lblStatusBar.ForeColor = Color.Orange; break; } processedRecords++; lblCurrentRecord.Text = $"正在迁移记录 {record.Id} ({processedRecords}/{totalRecords})..."; try { var migrationResult = await MigrateRecordAsync(record, _migrationCts.Token); if (migrationResult) { successCount++; UpdateRowStatus(record.Id, "已迁移", Color.Green); } else { failCount++; UpdateRowStatus(record.Id, "迁移失败", Color.Red); } } catch (OperationCanceledException) { break; } catch (Exception ex) { failCount++; UpdateRowStatus(record.Id, "迁移失败", Color.Red); System.Diagnostics.Debug.WriteLine($"迁移记录 {record.Id} 失败: {ex.Message}"); } // 更新进度 int progress = (int)((processedRecords * 100.0) / totalRecords); progressBar.Value = progress; lblProgress.Text = $"{progress}%"; } // 完成 if (!_migrationCts.Token.IsCancellationRequested) { progressBar.Value = 100; lblProgress.Text = "100%"; lblCurrentRecord.Text = $"迁移完成!成功: {successCount}, 失败: {failCount}"; lblStatusBar.Text = $"迁移完成,成功 {successCount} 条,失败 {failCount} 条"; lblStatusBar.ForeColor = failCount > 0 ? Color.Orange : Color.Green; MessageBox.Show( $"迁移完成!\n\n成功: {successCount} 条\n失败: {failCount} 条", "迁移结果", MessageBoxButtons.OK, failCount > 0 ? MessageBoxIcon.Warning : MessageBoxIcon.Information); } } catch (Exception ex) { ShowError($"迁移异常: {ex.Message}"); } finally { _isMigrating = false; _migrationCts?.Dispose(); _migrationCts = null; SetMigratingState(false); } } /// /// 迁移单条记录 /// private async Task MigrateRecordAsync(MigrationRecordDto record, CancellationToken cancellationToken) { var urlPairs = new List(); // 获取需要迁移的图片(未迁移的) var imagesToMigrate = record.Images.Where(img => !img.IsMigrated).ToList(); if (imagesToMigrate.Count == 0) { // 没有需要迁移的图片,直接返回成功 return true; } int imageIndex = 0; int totalImages = imagesToMigrate.Count; foreach (var image in imagesToMigrate) { if (cancellationToken.IsCancellationRequested) throw new OperationCanceledException(); imageIndex++; lblCurrentRecord.Text = $"正在迁移记录 {record.Id} - 图片 {imageIndex}/{totalImages}..."; try { // 1. 下载原图片 var (downloadSuccess, imageData, downloadMessage) = await _apiService.DownloadImageAsync(image.Url, cancellationToken); if (!downloadSuccess || imageData == null) { System.Diagnostics.Debug.WriteLine($"下载图片失败: {image.Url} - {downloadMessage}"); continue; // 跳过失败的图片,继续处理其他图片 } // 2. 生成 COS 路径 var recordTime = record.RecordTime ?? DateTime.Now; var fileName = CosService.GenerateFileName(".jpg"); var cosKey = CosService.GenerateCosKey(recordTime, "当日照片", "", fileName); // 3. 上传到 COS var (uploadSuccess, uploadMessage, cosUrl) = await _cosService.UploadBytesAsync(imageData, cosKey, cancellationToken: cancellationToken); if (!uploadSuccess || string.IsNullOrEmpty(cosUrl)) { System.Diagnostics.Debug.WriteLine($"上传图片失败: {uploadMessage}"); continue; // 跳过失败的图片,继续处理其他图片 } // 4. 记录 URL 映射 urlPairs.Add(new MigrationUrlPair { OldUrl = image.Url, NewUrl = cosUrl }); } catch (OperationCanceledException) { throw; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"处理图片异常: {image.Url} - {ex.Message}"); continue; // 跳过失败的图片,继续处理其他图片 } } // 5. 调用 API 更新 URL if (urlPairs.Count > 0) { var updateRequest = new MigrationUpdateRequest { RecordId = record.Id, ImageUrls = urlPairs }; var (updateSuccess, updateMessage) = await _apiService.UpdateMigrationUrlsAsync(updateRequest); if (!updateSuccess) { System.Diagnostics.Debug.WriteLine($"更新 URL 失败: {updateMessage}"); return false; } } // 如果所有图片都成功迁移,返回成功 return urlPairs.Count == imagesToMigrate.Count; } /// /// 更新行状态 /// private void UpdateRowStatus(int recordId, string status, Color color) { foreach (DataGridViewRow row in dgvRecords.Rows) { if (row.Cells[1].Value?.ToString() == recordId.ToString()) { row.Cells[6].Value = status; row.Cells[6].Style.ForeColor = color; break; } } } #endregion #region UI 辅助方法 /// /// 设置查询控件启用状态 /// private void SetQueryControlsEnabled(bool enabled) { dtpStartDate.Enabled = enabled; dtpEndDate.Enabled = enabled; cboStatus.Enabled = enabled; btnQuery.Enabled = enabled; } /// /// 设置迁移状态 /// private void SetMigratingState(bool migrating) { SetQueryControlsEnabled(!migrating); dgvRecords.Enabled = !migrating; chkSelectAll.Enabled = !migrating; if (migrating) { btnMigrate.Text = "取消迁移"; btnMigrate.BackColor = Color.FromArgb(220, 53, 69); } else { btnMigrate.Text = "开始迁移"; btnMigrate.BackColor = Color.FromArgb(40, 167, 69); progressBar.Value = 0; lblProgress.Text = ""; } } /// /// 更新迁移按钮状态 /// private void UpdateMigrateButtonState() { btnMigrate.Enabled = GetSelectedRecordIds().Count > 0 || _isMigrating; } /// /// 显示错误信息 /// private void ShowError(string message) { lblStatusBar.Text = message; lblStatusBar.ForeColor = Color.Red; } /// /// 截断内容 /// private static string TruncateContent(string content, int maxLength) { if (string.IsNullOrEmpty(content)) return ""; return content.Length <= maxLength ? content : content[..maxLength] + "..."; } /// /// 窗体关闭事件 /// private void MigrationForm_FormClosing(object sender, FormClosingEventArgs e) { if (_isMigrating) { var result = MessageBox.Show( "正在迁移中,确定要取消并退出吗?", "确认", MessageBoxButtons.YesNo, MessageBoxIcon.Question); if (result == DialogResult.No) { e.Cancel = true; return; } _migrationCts?.Cancel(); } // 释放 COS 服务 _cosService.Dispose(); } #endregion } }