WorkCamera/client/WorkCameraExport/Forms/MigrationForm.cs
2026-01-05 21:20:55 +08:00

584 lines
19 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;
namespace WorkCameraExport.Forms
{
/// <summary>
/// 迁移窗体 - 历史数据迁移功能
/// </summary>
public partial class MigrationForm : Form
{
private readonly ApiService _apiService;
private readonly CosService _cosService;
private CancellationTokenSource? _migrationCts;
private bool _isMigrating;
// 记录数据
private List<MigrationRecordDto> _records = new();
private int _totalRecords;
public MigrationForm(ApiService apiService)
{
InitializeComponent();
_apiService = apiService;
_cosService = new CosService();
}
/// <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;
try
{
var query = BuildQuery();
query.PageNum = 1;
query.PageSize = 50;
// 获取所有记录(分页获取)
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)
{
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();
}
}
/// <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_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex == 0 && e.RowIndex >= 0)
{
// 延迟更新,等待复选框状态变更
BeginInvoke(new Action(() =>
{
UpdateSelectedCount();
UpdateMigrateButtonState();
}));
}
}
/// <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;
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);
}
}
/// <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();
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;
}
/// <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
}
}