This commit is contained in:
zpc 2026-01-06 22:23:29 +08:00
parent 0708675fb5
commit 08aaf97f3d
12 changed files with 160 additions and 36 deletions

View File

@ -311,7 +311,7 @@ namespace WorkCameraExport.Tests
Address = $"地址{i}",
Content = $"工作内容{i}",
StatusName = "正常",
Workers = new List<WorkerDto> { new WorkerDto { Id = i, WorkerName = $"工人{i}" } },
Workers = new List<string> { $"工人{i}" },
Images = new List<string>(),
CreateTime = DateTime.Now.AddDays(-i),
UpdateTime = DateTime.Now
@ -331,7 +331,7 @@ namespace WorkCameraExport.Tests
Address = "测试地址",
Content = "测试内容",
StatusName = "正常",
Workers = new List<WorkerDto> { new WorkerDto { Id = 1, WorkerName = "测试工人" } },
Workers = new List<string> { "测试工人" },
Images = Enumerable.Range(1, imageCount)
.Select(i => $"http://test.local/image{i}.jpg")
.ToList(),

View File

@ -194,7 +194,7 @@ namespace WorkCameraExport.Forms
public void NavigateToMigration()
{
_logService?.Info("导航到数据迁移界面");
using var form = new MigrationForm(_apiService);
using var form = new MigrationForm(_apiService, _logService);
form.ShowDialog(this);
}

View File

@ -457,7 +457,7 @@ namespace WorkCameraExport.Forms
return;
}
using var migrationForm = new MigrationForm(_apiService);
using var migrationForm = new MigrationForm(_apiService, null);
migrationForm.ShowDialog(this);
}
}

View File

@ -226,7 +226,8 @@ namespace WorkCameraExport.Forms
dgvRecords.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
dgvRecords.Size = new Size(910, 315);
dgvRecords.TabIndex = 0;
dgvRecords.CellContentClick += dgvRecords_CellContentClick;
dgvRecords.CellValueChanged += dgvRecords_CellValueChanged;
dgvRecords.CurrentCellDirtyStateChanged += dgvRecords_CurrentCellDirtyStateChanged;
//
// colSelect

View File

@ -1,5 +1,6 @@
using WorkCameraExport.Models;
using WorkCameraExport.Services;
using WorkCameraExport.Services.Interfaces;
namespace WorkCameraExport.Forms
{
@ -10,6 +11,7 @@ namespace WorkCameraExport.Forms
{
private readonly ApiService _apiService;
private readonly CosService _cosService;
private readonly ILogService? _logService;
private CancellationTokenSource? _migrationCts;
private bool _isMigrating;
@ -17,11 +19,12 @@ namespace WorkCameraExport.Forms
private List<MigrationRecordDto> _records = new();
private int _totalRecords;
public MigrationForm(ApiService apiService)
public MigrationForm(ApiService apiService, ILogService? logService = null)
{
InitializeComponent();
_apiService = apiService;
_cosService = new CosService();
_logService = logService;
_cosService = new CosService(logService);
}
/// <summary>
@ -69,12 +72,16 @@ namespace WorkCameraExport.Forms
_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;
@ -86,6 +93,7 @@ namespace WorkCameraExport.Forms
if (!success || data == null)
{
_logService?.Error($"[迁移界面] 查询失败: {message}");
ShowError(message);
return;
}
@ -93,6 +101,8 @@ namespace WorkCameraExport.Forms
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;
@ -123,6 +133,7 @@ namespace WorkCameraExport.Forms
UpdateSelectedCount();
lblStatusBar.Text = $"查询完成,共 {_totalRecords} 条记录";
lblStatusBar.ForeColor = Color.Green;
_logService?.Info($"[迁移界面] 查询完成, 共 {_totalRecords} 条记录");
}
catch (Exception ex)
{
@ -188,18 +199,25 @@ namespace WorkCameraExport.Forms
}
/// <summary>
/// 数据网格单元格点击事件
/// 数据网格单元格值变更事件(用于复选框)
/// </summary>
private void dgvRecords_CellContentClick(object sender, DataGridViewCellEventArgs e)
private void dgvRecords_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex == 0 && e.RowIndex >= 0)
{
// 延迟更新,等待复选框状态变更
BeginInvoke(new Action(() =>
{
UpdateSelectedCount();
UpdateMigrateButtonState();
}));
}
}
/// <summary>
/// 数据网格当前单元格脏状态变更事件(立即提交复选框编辑)
/// </summary>
private void dgvRecords_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
if (dgvRecords.IsCurrentCellDirty && dgvRecords.CurrentCell is DataGridViewCheckBoxCell)
{
dgvRecords.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
}
@ -289,18 +307,24 @@ namespace WorkCameraExport.Forms
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);
@ -309,6 +333,7 @@ namespace WorkCameraExport.Forms
{
if (_migrationCts.Token.IsCancellationRequested)
{
_logService?.Info("[迁移界面] 迁移已取消");
lblStatusBar.Text = "迁移已取消";
lblStatusBar.ForeColor = Color.Orange;
break;
@ -316,6 +341,7 @@ namespace WorkCameraExport.Forms
processedRecords++;
lblCurrentRecord.Text = $"正在迁移记录 {record.Id} ({processedRecords}/{totalRecords})...";
_logService?.Info($"[迁移界面] 开始迁移记录 {record.Id}, 图片数={record.Images?.Count ?? 0}");
try
{
@ -324,23 +350,26 @@ namespace WorkCameraExport.Forms
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);
System.Diagnostics.Debug.WriteLine($"迁移记录 {record.Id} 失败: {ex.Message}");
}
// 更新进度
@ -357,6 +386,7 @@ namespace WorkCameraExport.Forms
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} 条",
@ -388,9 +418,12 @@ namespace WorkCameraExport.Forms
// 获取需要迁移的图片(未迁移的)
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;
}
@ -407,31 +440,39 @@ namespace WorkCameraExport.Forms
try
{
_logService?.Info($"[迁移] 记录 {record.Id} 图片 {imageIndex}/{totalImages}: 开始下载 {image.Url}");
// 1. 下载原图片
var (downloadSuccess, imageData, downloadMessage) =
await _apiService.DownloadImageAsync(image.Url, cancellationToken);
if (!downloadSuccess || imageData == null)
{
System.Diagnostics.Debug.WriteLine($"下载图片失败: {image.Url} - {downloadMessage}");
_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))
{
System.Diagnostics.Debug.WriteLine($"上传图片失败: {uploadMessage}");
_logService?.Warn($"[迁移] 记录 {record.Id} 上传COS失败: {uploadMessage}");
continue; // 跳过失败的图片,继续处理其他图片
}
_logService?.Info($"[迁移] 记录 {record.Id} 上传COS成功, NewUrl={cosUrl}");
// 4. 记录 URL 映射
urlPairs.Add(new MigrationUrlPair
{
@ -445,11 +486,13 @@ namespace WorkCameraExport.Forms
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"处理图片异常: {image.Url} - {ex.Message}");
_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)
{
@ -459,16 +502,21 @@ namespace WorkCameraExport.Forms
ImageUrls = urlPairs
};
_logService?.Info($"[迁移] 记录 {record.Id}: 调用API更新URL, 数量={urlPairs.Count}");
var (updateSuccess, updateMessage) = await _apiService.UpdateMigrationUrlsAsync(updateRequest);
if (!updateSuccess)
{
System.Diagnostics.Debug.WriteLine($"更新 URL 失败: {updateMessage}");
_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;
}

View File

@ -323,6 +323,15 @@ namespace WorkCameraExport.Services
if (response.IsSuccess && response.Data != null)
{
// 调试日志:记录反序列化后的 Workers 数据
foreach (var record in response.Data.Result.Take(3)) // 只记录前3条
{
var workersInfo = record.Workers != null
? $"Workers数量={record.Workers.Count}, 名称=[{string.Join(", ", record.Workers.Select(w => w.WorkerName ?? "null"))}]"
: "Workers=null";
_logService?.Info($"[反序列化] 记录ID={record.Id}, {workersInfo}");
}
return (true, "查询成功", response.Data);
}
@ -563,6 +572,7 @@ namespace WorkCameraExport.Services
{
try
{
_logService?.Info($"[迁移] 开始查询迁移列表, 页码={query.PageNum}, 状态={query.Status}");
await EnsureTokenValidAsync();
var queryParams = BuildQueryString(query);
var response = await ExecuteWithRetryAsync(() =>
@ -570,14 +580,16 @@ namespace WorkCameraExport.Services
if (response.IsSuccess && response.Data != null)
{
_logService?.Info($"[迁移] 查询成功, 共 {response.Data.TotalNum} 条记录");
return (true, "查询成功", response.Data);
}
_logService?.Warn($"[迁移] 查询失败: {response.Msg}, Code={response.Code}");
return (false, response.Msg ?? "查询失败", null);
}
catch (Exception ex)
{
_logService?.Error($"查询迁移列表异常: {ex.Message}", ex);
_logService?.Error($"[迁移] 查询迁移列表异常: {ex.Message}", ex);
return (false, $"查询异常: {ex.Message}", null);
}
}
@ -590,20 +602,23 @@ namespace WorkCameraExport.Services
{
try
{
_logService?.Info($"[迁移] 开始更新URL, 记录ID={request.RecordId}, 图片数={request.ImageUrls?.Count ?? 0}");
await EnsureTokenValidAsync();
var response = await ExecuteWithRetryAsync(() =>
PostAsync<object>("/api/workrecord/migration/update", request));
if (response.IsSuccess)
{
_logService?.Info($"[迁移] 更新URL成功, 记录ID={request.RecordId}");
return (true, "更新成功");
}
_logService?.Warn($"[迁移] 更新URL失败: {response.Msg}, Code={response.Code}");
return (false, response.Msg ?? "更新失败");
}
catch (Exception ex)
{
_logService?.Error($"更新迁移URL异常: {ex.Message}", ex);
_logService?.Error($"[迁移] 更新迁移URL异常: {ex.Message}", ex);
return (false, $"更新异常: {ex.Message}");
}
}
@ -769,6 +784,9 @@ namespace WorkCameraExport.Services
var content = await response.Content.ReadAsStringAsync();
// 调试日志:记录原始 JSON 响应(完整内容)
_logService?.Info($"[API响应] {endpoint} 原始JSON: {content}");
return JsonSerializer.Deserialize<PagedResult<T>>(content, JsonOptions)
?? new PagedResult<T> { Code = 500, Msg = "解析响应失败" };
}

View File

@ -3,6 +3,7 @@ using COSXML.Auth;
using COSXML.Model.Object;
using COSXML.Transfer;
using WorkCameraExport.Models;
using WorkCameraExport.Services.Interfaces;
namespace WorkCameraExport.Services
{
@ -13,14 +14,21 @@ namespace WorkCameraExport.Services
{
private CosXml? _cosXml;
private CosTempCredentials? _credentials;
private readonly ILogService? _logService;
private bool _disposed;
public CosService(ILogService? logService = null)
{
_logService = logService;
}
/// <summary>
/// 初始化 COS 客户端
/// </summary>
public void Initialize(CosTempCredentials credentials)
{
_credentials = credentials;
_logService?.Info($"[COS] 初始化, Region={credentials.Region}, Bucket={credentials.Bucket}");
var config = new CosXmlConfig.Builder()
.IsHttps(true)
@ -35,6 +43,7 @@ namespace WorkCameraExport.Services
credentials.SessionToken);
_cosXml = new CosXmlServer(config, credentialProvider);
_logService?.Info("[COS] 初始化完成");
}
/// <summary>
@ -58,9 +67,12 @@ namespace WorkCameraExport.Services
{
if (!IsInitialized || _cosXml == null || _credentials == null)
{
_logService?.Error("[COS] 上传失败: 服务未初始化");
return (false, "COS 服务未初始化", null);
}
_logService?.Info($"[COS] 开始上传文件: {localFilePath} -> {cosKey}");
try
{
var transferConfig = new TransferConfig();
@ -83,27 +95,33 @@ namespace WorkCameraExport.Services
if (result.IsSuccessful())
{
var cosUrl = $"https://{_credentials.Bucket}.cos.{_credentials.Region}.myqcloud.com/{cosKey}";
_logService?.Info($"[COS] 上传成功: {cosUrl}");
return (true, "上传成功", cosUrl);
}
else
{
_logService?.Warn($"[COS] 上传失败: result.IsSuccessful()=false");
return (false, "上传失败", null);
}
}
catch (OperationCanceledException)
{
_logService?.Info("[COS] 上传已取消");
return (false, "上传已取消", null);
}
catch (COSXML.CosException.CosClientException clientEx)
{
_logService?.Error($"[COS] 客户端异常: {clientEx.Message}", clientEx);
return (false, $"客户端异常: {clientEx.Message}", null);
}
catch (COSXML.CosException.CosServerException serverEx)
{
_logService?.Error($"[COS] 服务端异常: {serverEx.GetInfo()}");
return (false, $"服务端异常: {serverEx.GetInfo()}", null);
}
catch (Exception ex)
{
_logService?.Error($"[COS] 上传异常: {ex.Message}", ex);
return (false, $"上传异常: {ex.Message}", null);
}
}

View File

@ -201,6 +201,12 @@ namespace WorkCameraExport.Services
WorkRecordExportItem record,
CancellationToken cancellationToken)
{
// 调试日志:记录写入 Excel 时的 Workers 数据
var workersValue = record.Workers != null
? $"Workers数量={record.Workers.Count}, 值=[{string.Join(", ", record.Workers)}]"
: "Workers=null";
_logService?.Info($"[Excel写入] 行{row}, 记录ID={record.Id}, {workersValue}");
worksheet.Cells[row, 1].Value = row - 1; // 序号
worksheet.Cells[row, 2].Value = record.DeptName;
// 第3列是图片稍后处理

View File

@ -318,7 +318,20 @@ namespace WorkCameraExport.Services
{
return records.Select(r =>
{
// 调试日志:记录 FromDto 前的 Workers 数据
var workersInfo = r.Workers != null
? $"Workers数量={r.Workers.Count}, 值=[{string.Join(", ", r.Workers)}]"
: "Workers=null";
_logService?.Info($"[BuildExportItems] 记录ID={r.Id}, {workersInfo}");
var item = WorkRecordExportItem.FromDto(r);
// 调试日志:记录 FromDto 后的 Workers 数据
var itemWorkersInfo = item.Workers != null
? $"Workers数量={item.Workers.Count}, 值=[{string.Join(", ", item.Workers)}]"
: "Workers=null";
_logService?.Info($"[BuildExportItems->FromDto后] 记录ID={item.Id}, {itemWorkersInfo}");
item.ImagePaths = r.Images
.Where(url => !string.IsNullOrEmpty(url) && imagePathMap.ContainsKey(url))
.Select(url => imagePathMap[url])
@ -334,6 +347,17 @@ namespace WorkCameraExport.Services
/// </summary>
private WorkRecordExportDto ConvertToExportDto(WorkRecordDto dto)
{
// 调试日志:记录转换前的 Workers 数据
var workersBeforeConvert = dto.Workers != null
? $"Workers数量={dto.Workers.Count}, 名称=[{string.Join(", ", dto.Workers.Select(w => w.WorkerName ?? "null"))}]"
: "Workers=null";
_logService?.Info($"[转换前] 记录ID={dto.Id}, {workersBeforeConvert}");
var workerNames = dto.Workers?.Select(w => w.WorkerName).ToList() ?? new List<string>();
// 调试日志:记录转换后的 Workers 数据
_logService?.Info($"[转换后] 记录ID={dto.Id}, WorkerNames=[{string.Join(", ", workerNames)}]");
return new WorkRecordExportDto
{
Id = dto.Id,
@ -344,7 +368,7 @@ namespace WorkCameraExport.Services
Address = dto.Address,
Content = dto.Content,
StatusName = dto.StatusName,
Workers = dto.Workers?.Select(w => w.WorkerName).ToList() ?? new List<string>(),
Workers = workerNames,
Images = dto.Images.Select(i => i.Url).ToList(),
CreateTime = dto.CreateTime,
UpdateTime = dto.UpdateTime

View File

@ -105,8 +105,10 @@ namespace ZR.Admin.WebApi.Controllers.Business
var info = response.Adapt<CamWorkrecordDto>();
if (info != null)
{
var names = _CamWorkerService.AsQueryable().Where(it => it.WorkrecordId == info.Id).Select(it => it.WorkerName).ToList();
info.Worker = names != null && names.Count > 0 ? string.Join(",", names) : "";
// 获取工作人员列表
var workers = _CamWorkerService.AsQueryable().Where(it => it.WorkrecordId == info.Id).ToList();
info.Workers = workers.Select(w => new CamWorkersDto { Id = w.Id, WorkerName = w.WorkerName }).ToList();
info.Worker = workers.Count > 0 ? string.Join(",", workers.Select(w => w.WorkerName)) : "";
// 获取图片列表
var imageRecords = _CamWorkrecordImageService.GetListByWorkrecordId(info.Id);

View File

@ -2,8 +2,8 @@
"name" : "随工水印相机",
"appid" : "__UNI__37E1E71",
"description" : "",
"versionName" : "1.0.5",
"versionCode" : 105,
"versionName" : "1.0.5.03",
"versionCode" : 106,
"transformPx" : false,
/* 5+App */
"app-plus" : {

View File

@ -776,10 +776,17 @@ const handleSaveImage = async () => {
await addWatermarkToImage();
//
if (imageSrc.value) {
await saveImageToPhotosAlbum(imageSrc.value);
// if (imageSrc.value) {
// await saveImageToPhotosAlbum(imageSrc.value);
// }
try {
if (imageList.value.length > 0) {
const watermarkPaths = imageList.value.map(img => img.watermark);
await saveImagesToPhotosAlbum(watermarkPaths);
}
} catch (saveError) {
console.error("保存图片到相册失败:", saveError);
}
uni.hideLoading();
uni.showToast({
title: "图片保存成功",
@ -914,8 +921,8 @@ const fallbackToV2Upload = async (_remarks, validWorkers) => {
//
try {
if (imageList.value.length > 0) {
const watermarkPaths = imageList.value.map(img => img.watermark);
await saveImagesToPhotosAlbum(watermarkPaths);
//const watermarkPaths = imageList.value.map(img => img.watermark);
//await saveImagesToPhotosAlbum(watermarkPaths);
}
} catch (saveError) {
console.error("保存图片到相册失败:", saveError);
@ -1083,8 +1090,8 @@ const handleSaveAndSubmit = async () => {
try {
//
if (imageList.value.length > 0) {
const watermarkPaths = imageList.value.map(img => img.watermark);
const saveResult = await saveImagesToPhotosAlbum(watermarkPaths);
// const watermarkPaths = imageList.value.map(img => img.watermark);
// const saveResult = await saveImagesToPhotosAlbum(watermarkPaths);
console.log(`保存到相册完成: 成功${saveResult.success}张, 失败${saveResult.failed}`);
}
} catch (error) {