WorkCamera/client/WorkCameraExport/Forms/MigrationForm.cs
2026-01-06 22:23:29 +08:00

632 lines
22 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using WorkCameraExport.Models;
using WorkCameraExport.Services;
using WorkCameraExport.Services.Interfaces;
namespace WorkCameraExport.Forms
{
/// <summary>
/// 迁移窗体 - 历史数据迁移功能
/// </summary>
public partial class MigrationForm : Form
{
private readonly ApiService _apiService;
private readonly CosService _cosService;
private readonly ILogService? _logService;
private CancellationTokenSource? _migrationCts;
private bool _isMigrating;
// 记录数据
private List<MigrationRecordDto> _records = new();
private int _totalRecords;
public MigrationForm(ApiService apiService, ILogService? logService = null)
{
InitializeComponent();
_apiService = apiService;
_logService = logService;
_cosService = new CosService(logService);
}
/// <summary>
/// 窗体加载事件
/// </summary>
private void MigrationForm_Load(object sender, EventArgs e)
{
InitializeDefaults();
}
/// <summary>
/// 初始化默认值
/// </summary>
private void InitializeDefaults()
{
// 设置默认日期范围最近90天
dtpEndDate.Value = DateTime.Today;
dtpStartDate.Value = DateTime.Today.AddDays(-90);
// 设置默认状态为"未迁移"
cboStatus.SelectedIndex = 1; // 未迁移
UpdateMigrateButtonState();
}
#region
/// <summary>
/// 查询按钮点击事件
/// </summary>
private async void btnQuery_Click(object sender, EventArgs e)
{
await DoQueryAsync();
}
/// <summary>
/// 执行查询
/// </summary>
private async Task DoQueryAsync()
{
SetQueryControlsEnabled(false);
lblStatusBar.Text = "正在查询...";
lblStatusBar.ForeColor = Color.Blue;
dgvRecords.Rows.Clear();
_records.Clear();
_totalRecords = 0;
_logService?.Info("[迁移界面] 开始查询迁移记录");
try
{
var query = BuildQuery();
query.PageNum = 1;
query.PageSize = 50;
_logService?.Info($"[迁移界面] 查询条件: 开始={query.StartDate}, 结束={query.EndDate}, 状态={query.Status}");
// 获取所有记录(分页获取)
var allRecords = new List<MigrationRecordDto>();
int pageNum = 1;
while (true)
{
query.PageNum = pageNum;
var (success, message, data) = await _apiService.GetMigrationListAsync(query);
if (!success || data == null)
{
_logService?.Error($"[迁移界面] 查询失败: {message}");
ShowError(message);
return;
}
allRecords.AddRange(data.Result);
_totalRecords = data.TotalNum;
_logService?.Info($"[迁移界面] 第{pageNum}页获取 {data.Result.Count} 条, 累计 {allRecords.Count}/{_totalRecords}");
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;
_logService?.Info($"[迁移界面] 查询完成, 共 {_totalRecords} 条记录");
}
catch (Exception ex)
{
ShowError($"查询异常: {ex.Message}");
}
finally
{
SetQueryControlsEnabled(true);
UpdateMigrateButtonState();
}
}
/// <summary>
/// 构建查询参数
/// </summary>
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
};
}
/// <summary>
/// 获取状态文本
/// </summary>
private static string GetStatusText(int status)
{
return status switch
{
0 => "未迁移",
1 => "已迁移",
2 => "迁移失败",
_ => "未知"
};
}
#endregion
#region
/// <summary>
/// 全选/取消复选框变更事件
/// </summary>
private void chkSelectAll_CheckedChanged(object sender, EventArgs e)
{
foreach (DataGridViewRow row in dgvRecords.Rows)
{
row.Cells[0].Value = chkSelectAll.Checked;
}
UpdateSelectedCount();
UpdateMigrateButtonState();
}
/// <summary>
/// 数据网格单元格值变更事件(用于复选框)
/// </summary>
private void dgvRecords_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex == 0 && e.RowIndex >= 0)
{
UpdateSelectedCount();
UpdateMigrateButtonState();
}
}
/// <summary>
/// 数据网格当前单元格脏状态变更事件(立即提交复选框编辑)
/// </summary>
private void dgvRecords_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
if (dgvRecords.IsCurrentCellDirty && dgvRecords.CurrentCell is DataGridViewCheckBoxCell)
{
dgvRecords.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
}
/// <summary>
/// 更新已选数量
/// </summary>
private void UpdateSelectedCount()
{
int selectedCount = GetSelectedRecordIds().Count;
lblSelectedRecords.Text = $"已选 {selectedCount} 条";
}
/// <summary>
/// 获取选中的记录ID列表
/// </summary>
private List<int> GetSelectedRecordIds()
{
var selectedIds = new List<int>();
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;
}
/// <summary>
/// 获取选中的记录
/// </summary>
private List<MigrationRecordDto> GetSelectedRecords()
{
var selectedIds = GetSelectedRecordIds();
return _records.Where(r => selectedIds.Contains(r.Id)).ToList();
}
#endregion
#region
/// <summary>
/// 迁移按钮点击事件
/// </summary>
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);
}
/// <summary>
/// 执行迁移
/// </summary>
private async Task DoMigrationAsync(List<MigrationRecordDto> records)
{
_isMigrating = true;
_migrationCts = new CancellationTokenSource();
SetMigratingState(true);
int successCount = 0;
int failCount = 0;
int totalRecords = records.Count;
int processedRecords = 0;
_logService?.Info($"[迁移界面] 开始迁移, 共 {totalRecords} 条记录");
try
{
// 获取 COS 临时密钥
lblCurrentRecord.Text = "正在获取 COS 临时密钥...";
_logService?.Info("[迁移界面] 正在获取 COS 临时密钥...");
var (credSuccess, credMessage, credentials) = await _apiService.GetTempCredentialsAsync();
if (!credSuccess || credentials == null)
{
_logService?.Error($"[迁移界面] 获取 COS 临时密钥失败: {credMessage}");
ShowError($"获取 COS 临时密钥失败: {credMessage}");
return;
}
_logService?.Info($"[迁移界面] COS 临时密钥获取成功, Bucket={credentials.Bucket}, Region={credentials.Region}");
// 初始化 COS 服务
_cosService.Initialize(credentials);
// 逐条迁移记录
foreach (var record in records)
{
if (_migrationCts.Token.IsCancellationRequested)
{
_logService?.Info("[迁移界面] 迁移已取消");
lblStatusBar.Text = "迁移已取消";
lblStatusBar.ForeColor = Color.Orange;
break;
}
processedRecords++;
lblCurrentRecord.Text = $"正在迁移记录 {record.Id} ({processedRecords}/{totalRecords})...";
_logService?.Info($"[迁移界面] 开始迁移记录 {record.Id}, 图片数={record.Images?.Count ?? 0}");
try
{
var migrationResult = await MigrateRecordAsync(record, _migrationCts.Token);
if (migrationResult)
{
successCount++;
_logService?.Info($"[迁移界面] 记录 {record.Id} 迁移成功");
UpdateRowStatus(record.Id, "已迁移", Color.Green);
}
else
{
failCount++;
_logService?.Warn($"[迁移界面] 记录 {record.Id} 迁移失败");
UpdateRowStatus(record.Id, "迁移失败", Color.Red);
}
}
catch (OperationCanceledException)
{
_logService?.Info("[迁移界面] 迁移操作被取消");
break;
}
catch (Exception ex)
{
failCount++;
_logService?.Error($"[迁移界面] 记录 {record.Id} 迁移异常: {ex.Message}", ex);
UpdateRowStatus(record.Id, "迁移失败", Color.Red);
}
// 更新进度
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;
_logService?.Info($"[迁移界面] 迁移完成, 成功={successCount}, 失败={failCount}");
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);
}
}
/// <summary>
/// 迁移单条记录
/// </summary>
private async Task<bool> MigrateRecordAsync(MigrationRecordDto record, CancellationToken cancellationToken)
{
var urlPairs = new List<MigrationUrlPair>();
// 获取需要迁移的图片(未迁移的)
var imagesToMigrate = record.Images.Where(img => !img.IsMigrated).ToList();
_logService?.Info($"[迁移] 记录 {record.Id}: 总图片={record.Images?.Count ?? 0}, 待迁移={imagesToMigrate.Count}");
if (imagesToMigrate.Count == 0)
{
// 没有需要迁移的图片,直接返回成功
_logService?.Info($"[迁移] 记录 {record.Id}: 无需迁移的图片, 跳过");
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
{
_logService?.Info($"[迁移] 记录 {record.Id} 图片 {imageIndex}/{totalImages}: 开始下载 {image.Url}");
// 1. 下载原图片
var (downloadSuccess, imageData, downloadMessage) =
await _apiService.DownloadImageAsync(image.Url, cancellationToken);
if (!downloadSuccess || imageData == null)
{
_logService?.Warn($"[迁移] 记录 {record.Id} 图片下载失败: {downloadMessage}, URL={image.Url}");
continue; // 跳过失败的图片,继续处理其他图片
}
_logService?.Info($"[迁移] 记录 {record.Id} 图片下载成功, 大小={imageData.Length} bytes");
// 2. 生成 COS 路径
var recordTime = record.RecordTime ?? DateTime.Now;
var fileName = CosService.GenerateFileName(".jpg");
var cosKey = CosService.GenerateCosKey(recordTime, "当日照片", "", fileName);
_logService?.Info($"[迁移] 记录 {record.Id} 准备上传到 COS, Key={cosKey}");
// 3. 上传到 COS
var (uploadSuccess, uploadMessage, cosUrl) =
await _cosService.UploadBytesAsync(imageData, cosKey, cancellationToken: cancellationToken);
if (!uploadSuccess || string.IsNullOrEmpty(cosUrl))
{
_logService?.Warn($"[迁移] 记录 {record.Id} 上传COS失败: {uploadMessage}");
continue; // 跳过失败的图片,继续处理其他图片
}
_logService?.Info($"[迁移] 记录 {record.Id} 上传COS成功, NewUrl={cosUrl}");
// 4. 记录 URL 映射
urlPairs.Add(new MigrationUrlPair
{
OldUrl = image.Url,
NewUrl = cosUrl
});
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
_logService?.Error($"[迁移] 记录 {record.Id} 处理图片异常: {ex.Message}, URL={image.Url}", ex);
continue; // 跳过失败的图片,继续处理其他图片
}
}
_logService?.Info($"[迁移] 记录 {record.Id}: 成功处理 {urlPairs.Count}/{totalImages} 张图片");
// 5. 调用 API 更新 URL
if (urlPairs.Count > 0)
{
var updateRequest = new MigrationUpdateRequest
{
RecordId = record.Id,
ImageUrls = urlPairs
};
_logService?.Info($"[迁移] 记录 {record.Id}: 调用API更新URL, 数量={urlPairs.Count}");
var (updateSuccess, updateMessage) = await _apiService.UpdateMigrationUrlsAsync(updateRequest);
if (!updateSuccess)
{
_logService?.Error($"[迁移] 记录 {record.Id} 更新URL失败: {updateMessage}");
return false;
}
_logService?.Info($"[迁移] 记录 {record.Id}: API更新URL成功");
}
else
{
_logService?.Warn($"[迁移] 记录 {record.Id}: 没有成功迁移的图片, 不调用更新API");
} // 如果所有图片都成功迁移,返回成功
return urlPairs.Count == imagesToMigrate.Count;
}
/// <summary>
/// 更新行状态
/// </summary>
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
/// <summary>
/// 设置查询控件启用状态
/// </summary>
private void SetQueryControlsEnabled(bool enabled)
{
dtpStartDate.Enabled = enabled;
dtpEndDate.Enabled = enabled;
cboStatus.Enabled = enabled;
btnQuery.Enabled = enabled;
}
/// <summary>
/// 设置迁移状态
/// </summary>
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 = "";
}
}
/// <summary>
/// 更新迁移按钮状态
/// </summary>
private void UpdateMigrateButtonState()
{
btnMigrate.Enabled = GetSelectedRecordIds().Count > 0 || _isMigrating;
}
/// <summary>
/// 显示错误信息
/// </summary>
private void ShowError(string message)
{
lblStatusBar.Text = message;
lblStatusBar.ForeColor = Color.Red;
}
/// <summary>
/// 截断内容
/// </summary>
private static string TruncateContent(string content, int maxLength)
{
if (string.IsNullOrEmpty(content)) return "";
return content.Length <= maxLength ? content : content[..maxLength] + "...";
}
/// <summary>
/// 窗体关闭事件
/// </summary>
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
}
}