using WorkCameraExport.Models;
using WorkCameraExport.Services;
using WorkCameraExport.Services.Interfaces;
namespace WorkCameraExport.Forms
{
///
/// 迁移窗体 - 历史数据迁移功能
///
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 _records = new();
private int _totalRecords;
public MigrationForm(ApiService apiService, ILogService? logService = null)
{
InitializeComponent();
_apiService = apiService;
_logService = logService;
_cosService = new CosService(logService);
}
///
/// 窗体加载事件
///
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;
_logService?.Info("[迁移界面] 开始查询迁移记录");
try
{
var query = BuildQuery();
query.PageNum = 1;
query.PageSize = 50;
_logService?.Info($"[迁移界面] 查询条件: 开始={query.StartDate}, 结束={query.EndDate}, 状态={query.Status}");
// 获取所有记录(分页获取)
var allRecords = new List();
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();
}
}
///
/// 构建查询参数
///
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_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex == 0 && e.RowIndex >= 0)
{
UpdateSelectedCount();
UpdateMigrateButtonState();
}
}
///
/// 数据网格当前单元格脏状态变更事件(立即提交复选框编辑)
///
private void dgvRecords_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
if (dgvRecords.IsCurrentCellDirty && dgvRecords.CurrentCell is DataGridViewCheckBoxCell)
{
dgvRecords.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
}
///
/// 更新已选数量
///
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;
_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);
}
}
///
/// 迁移单条记录
///
private async Task MigrateRecordAsync(MigrationRecordDto record, CancellationToken cancellationToken)
{
var urlPairs = new List();
// 获取需要迁移的图片(未迁移的)
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;
}
///
/// 更新行状态
///
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
}
}