692 lines
26 KiB
C#
692 lines
26 KiB
C#
using System.IO.Compression;
|
||
using WorkCameraExport.Models;
|
||
using WorkCameraExport.Services.Interfaces;
|
||
|
||
namespace WorkCameraExport.Services
|
||
{
|
||
/// <summary>
|
||
/// 导出服务实现 - 负责在客户端本地生成 Excel 和 ZIP 文件
|
||
/// </summary>
|
||
public class ExportService : IExportService
|
||
{
|
||
private readonly IApiService _apiService;
|
||
private readonly IImageService _imageService;
|
||
private readonly IConfigService _configService;
|
||
private readonly ILogService? _logService;
|
||
private readonly ExcelService _excelService;
|
||
|
||
// 配置常量
|
||
private const int DefaultPageSize = 50;
|
||
private const int DefaultConcurrency = 5;
|
||
private const int DefaultImageQuality = 50;
|
||
|
||
public ExportService(
|
||
IApiService apiService,
|
||
IImageService imageService,
|
||
IConfigService configService,
|
||
ILogService? logService = null)
|
||
{
|
||
_apiService = apiService;
|
||
_imageService = imageService;
|
||
_configService = configService;
|
||
_logService = logService;
|
||
_excelService = new ExcelService(logService);
|
||
}
|
||
|
||
#region 工作记录导出
|
||
|
||
/// <summary>
|
||
/// 导出工作记录到 Excel(按查询条件导出全部)
|
||
/// </summary>
|
||
public async Task ExportWorkRecordsAsync(
|
||
WorkRecordQueryDto query,
|
||
string outputPath,
|
||
IProgress<ExportProgress>? progress = null,
|
||
CancellationToken cancellationToken = default)
|
||
{
|
||
var tempDir = CreateTempDirectory();
|
||
try
|
||
{
|
||
_logService?.Info($"[导出] 开始导出工作记录: {outputPath}");
|
||
_logService?.Info($"[导出] 临时目录: {tempDir}");
|
||
|
||
// 确保输出目录存在
|
||
var outputDir = Path.GetDirectoryName(outputPath);
|
||
if (!string.IsNullOrEmpty(outputDir) && !Directory.Exists(outputDir))
|
||
{
|
||
Directory.CreateDirectory(outputDir);
|
||
_logService?.Info($"[导出] 创建输出目录: {outputDir}");
|
||
}
|
||
|
||
// 获取配置
|
||
var config = _configService.LoadConfig();
|
||
var concurrency = config?.ImageDownloadConcurrency ?? DefaultConcurrency;
|
||
var imageQuality = config?.ImageCompressQuality ?? DefaultImageQuality;
|
||
|
||
// 1. 分页获取所有数据
|
||
_logService?.Info("[导出] 开始获取数据...");
|
||
var allRecords = await FetchAllRecordsAsync(query, progress, cancellationToken);
|
||
_logService?.Info($"[导出] 获取到 {allRecords.Count} 条记录");
|
||
|
||
if (allRecords.Count == 0)
|
||
{
|
||
_logService?.Warn("[导出] 没有找到符合条件的工作记录");
|
||
throw new InvalidOperationException("没有找到符合条件的工作记录");
|
||
}
|
||
|
||
// 2. 收集所有图片 URL
|
||
var allImageUrls = allRecords
|
||
.SelectMany(r => r.Images)
|
||
.Where(url => !string.IsNullOrEmpty(url))
|
||
.Distinct()
|
||
.ToList();
|
||
_logService?.Info($"[导出] 共 {allImageUrls.Count} 张图片需要下载");
|
||
|
||
ReportProgress(progress, allRecords.Count, 0, allImageUrls.Count, 0, "正在下载图片...");
|
||
|
||
// 3. 并发下载图片
|
||
var imagePathMap = await DownloadImagesAsync(
|
||
allImageUrls, tempDir, concurrency, imageQuality,
|
||
downloaded => ReportProgress(progress, allRecords.Count, 0, allImageUrls.Count, downloaded, "正在下载图片..."),
|
||
cancellationToken);
|
||
_logService?.Info($"[导出] 图片下载完成,成功 {imagePathMap.Count(x => x.Value != null)} 张");
|
||
|
||
// 4. 构建导出数据
|
||
var exportItems = BuildExportItems(allRecords, imagePathMap);
|
||
_logService?.Info($"[导出] 构建导出数据完成,共 {exportItems.Count} 条");
|
||
|
||
ReportProgress(progress, allRecords.Count, allRecords.Count, allImageUrls.Count, allImageUrls.Count, "正在生成 Excel...");
|
||
|
||
// 5. 生成 Excel
|
||
_logService?.Info($"[导出] 开始生成 Excel: {outputPath}");
|
||
await _excelService.ExportWorkRecordsToExcelAsync(exportItems, outputPath, cancellationToken);
|
||
|
||
// 验证文件是否生成
|
||
if (File.Exists(outputPath))
|
||
{
|
||
var fileInfo = new FileInfo(outputPath);
|
||
_logService?.Info($"[导出] Excel 文件已生成: {outputPath}, 大小: {fileInfo.Length} 字节");
|
||
}
|
||
else
|
||
{
|
||
_logService?.Error($"[导出] Excel 文件未生成: {outputPath}");
|
||
}
|
||
|
||
ReportProgress(progress, allRecords.Count, allRecords.Count, allImageUrls.Count, allImageUrls.Count, "导出完成");
|
||
_logService?.Info($"[导出] 工作记录导出完成: {allRecords.Count} 条记录, {allImageUrls.Count} 张图片");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logService?.Error($"[导出] 导出异常: {ex.Message}", ex);
|
||
throw;
|
||
}
|
||
finally
|
||
{
|
||
// 清理临时目录
|
||
CleanupTempDirectory(tempDir);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 导出指定 ID 的工作记录到 Excel
|
||
/// </summary>
|
||
public async Task ExportWorkRecordsByIdsAsync(
|
||
List<int> ids,
|
||
string outputPath,
|
||
IProgress<ExportProgress>? progress = null,
|
||
CancellationToken cancellationToken = default)
|
||
{
|
||
if (ids == null || ids.Count == 0)
|
||
{
|
||
throw new ArgumentException("请选择要导出的记录", nameof(ids));
|
||
}
|
||
|
||
var tempDir = CreateTempDirectory();
|
||
try
|
||
{
|
||
_logService?.Info($"开始导出选中的工作记录: {ids.Count} 条");
|
||
|
||
var config = _configService.LoadConfig();
|
||
var concurrency = config?.ImageDownloadConcurrency ?? DefaultConcurrency;
|
||
var imageQuality = config?.ImageCompressQuality ?? DefaultImageQuality;
|
||
|
||
// 1. 逐个获取记录详情
|
||
var allRecords = new List<WorkRecordExportDto>();
|
||
for (int i = 0; i < ids.Count; i++)
|
||
{
|
||
cancellationToken.ThrowIfCancellationRequested();
|
||
ReportProgress(progress, ids.Count, i, 0, 0, $"正在获取记录 {i + 1}/{ids.Count}...");
|
||
|
||
var result = await _apiService.GetWorkRecordAsync(ids[i]);
|
||
if (result.Success && result.Data != null)
|
||
{
|
||
allRecords.Add(ConvertToExportDto(result.Data));
|
||
}
|
||
}
|
||
|
||
if (allRecords.Count == 0)
|
||
{
|
||
throw new InvalidOperationException("没有找到有效的工作记录");
|
||
}
|
||
|
||
// 2. 收集所有图片 URL
|
||
var allImageUrls = allRecords
|
||
.SelectMany(r => r.Images)
|
||
.Where(url => !string.IsNullOrEmpty(url))
|
||
.Distinct()
|
||
.ToList();
|
||
|
||
ReportProgress(progress, allRecords.Count, allRecords.Count, allImageUrls.Count, 0, "正在下载图片...");
|
||
|
||
// 3. 并发下载图片
|
||
var imagePathMap = await DownloadImagesAsync(
|
||
allImageUrls, tempDir, concurrency, imageQuality,
|
||
downloaded => ReportProgress(progress, allRecords.Count, allRecords.Count, allImageUrls.Count, downloaded, "正在下载图片..."),
|
||
cancellationToken);
|
||
|
||
// 4. 构建导出数据
|
||
var exportItems = BuildExportItems(allRecords, imagePathMap);
|
||
|
||
ReportProgress(progress, allRecords.Count, allRecords.Count, allImageUrls.Count, allImageUrls.Count, "正在生成 Excel...");
|
||
|
||
// 5. 生成 Excel
|
||
await _excelService.ExportWorkRecordsToExcelAsync(exportItems, outputPath, cancellationToken);
|
||
|
||
ReportProgress(progress, allRecords.Count, allRecords.Count, allImageUrls.Count, allImageUrls.Count, "导出完成");
|
||
_logService?.Info($"选中记录导出完成: {allRecords.Count} 条记录");
|
||
}
|
||
finally
|
||
{
|
||
CleanupTempDirectory(tempDir);
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 分页获取所有记录
|
||
/// </summary>
|
||
private async Task<List<WorkRecordExportDto>> FetchAllRecordsAsync(
|
||
WorkRecordQueryDto query,
|
||
IProgress<ExportProgress>? progress,
|
||
CancellationToken cancellationToken)
|
||
{
|
||
var allRecords = new List<WorkRecordExportDto>();
|
||
var pageNum = 1;
|
||
var pageSize = DefaultPageSize;
|
||
int totalRecords = 0;
|
||
|
||
do
|
||
{
|
||
cancellationToken.ThrowIfCancellationRequested();
|
||
|
||
var pagedQuery = new WorkRecordQueryDto
|
||
{
|
||
PageNum = pageNum,
|
||
PageSize = pageSize,
|
||
StartDate = query.StartDate,
|
||
EndDate = query.EndDate,
|
||
DeptName = query.DeptName,
|
||
Address = query.Address,
|
||
Content = query.Content,
|
||
WorkerName = query.WorkerName,
|
||
StatusName = query.StatusName
|
||
};
|
||
|
||
var result = await _apiService.GetWorkRecordsAsync(pagedQuery);
|
||
if (!result.Success || result.Data == null)
|
||
{
|
||
_logService?.Error($"获取工作记录失败: {result.Message}");
|
||
throw new Exception($"获取工作记录失败: {result.Message}");
|
||
}
|
||
|
||
if (pageNum == 1)
|
||
{
|
||
totalRecords = result.Data.TotalNum;
|
||
ReportProgress(progress, totalRecords, 0, 0, 0, $"正在获取数据 (共 {totalRecords} 条)...");
|
||
}
|
||
|
||
// 转换为导出 DTO
|
||
foreach (var record in result.Data.Result)
|
||
{
|
||
allRecords.Add(ConvertToExportDto(record));
|
||
}
|
||
|
||
ReportProgress(progress, totalRecords, allRecords.Count, 0, 0, $"正在获取数据 {allRecords.Count}/{totalRecords}...");
|
||
|
||
// 检查是否还有更多数据
|
||
if (result.Data.Result.Count < pageSize || allRecords.Count >= totalRecords)
|
||
{
|
||
break;
|
||
}
|
||
|
||
pageNum++;
|
||
} while (true);
|
||
|
||
return allRecords;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 下载图片并返回 URL 到本地路径的映射
|
||
/// </summary>
|
||
private async Task<Dictionary<string, string?>> DownloadImagesAsync(
|
||
List<string> imageUrls,
|
||
string outputDir,
|
||
int concurrency,
|
||
int quality,
|
||
Action<int>? progressCallback,
|
||
CancellationToken cancellationToken)
|
||
{
|
||
if (imageUrls.Count == 0)
|
||
{
|
||
return new Dictionary<string, string?>();
|
||
}
|
||
|
||
var downloadProgress = new Progress<int>(count => progressCallback?.Invoke(count));
|
||
|
||
var results = await _imageService.DownloadImagesAsync(
|
||
imageUrls,
|
||
outputDir,
|
||
concurrency,
|
||
downloadProgress,
|
||
cancellationToken);
|
||
|
||
// 压缩下载的图片
|
||
foreach (var kvp in results.Where(r => r.Value != null))
|
||
{
|
||
try
|
||
{
|
||
var originalPath = kvp.Value!;
|
||
var imageData = await File.ReadAllBytesAsync(originalPath, cancellationToken);
|
||
var compressedData = _imageService.CompressAndResizeImage(imageData, 100, 60, quality);
|
||
await File.WriteAllBytesAsync(originalPath, compressedData, cancellationToken);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logService?.Warn($"压缩图片失败: {kvp.Key}, {ex.Message}");
|
||
}
|
||
}
|
||
|
||
return results;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 构建导出项列表
|
||
/// </summary>
|
||
private List<WorkRecordExportItem> BuildExportItems(
|
||
List<WorkRecordExportDto> records,
|
||
Dictionary<string, string?> imagePathMap)
|
||
{
|
||
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])
|
||
.Where(path => path != null)
|
||
.Cast<string>()
|
||
.ToList();
|
||
return item;
|
||
}).ToList();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 转换 WorkRecordDto 为 WorkRecordExportDto
|
||
/// </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,
|
||
DeptName = dto.DeptName,
|
||
RecordTime = dto.RecordTime,
|
||
Longitude = dto.Longitude,
|
||
Latitude = dto.Latitude,
|
||
Address = dto.Address,
|
||
Content = dto.Content,
|
||
StatusName = dto.StatusName,
|
||
Workers = workerNames,
|
||
Images = dto.Images.Select(i => i.Url).ToList(),
|
||
CreateTime = dto.CreateTime,
|
||
UpdateTime = dto.UpdateTime
|
||
};
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 月报表导出
|
||
|
||
/// <summary>
|
||
/// 导出月报表到 Excel
|
||
/// </summary>
|
||
public async Task ExportMonthlyReportAsync(
|
||
List<MonthlyReportDto> data,
|
||
string outputPath)
|
||
{
|
||
if (data == null || data.Count == 0)
|
||
{
|
||
throw new ArgumentException("没有数据可导出", nameof(data));
|
||
}
|
||
|
||
_logService?.Info($"开始导出月报表: {data.Count} 条记录");
|
||
await _excelService.ExportMonthlyReportToExcelAsync(data, outputPath);
|
||
_logService?.Info($"月报表导出完成: {outputPath}");
|
||
}
|
||
|
||
#endregion
|
||
|
||
|
||
#region 照片 ZIP 下载
|
||
|
||
/// <summary>
|
||
/// 下载指定月份的照片并打包成 ZIP
|
||
/// </summary>
|
||
public async Task DownloadPhotosZipAsync(
|
||
string yearMonth,
|
||
string outputPath,
|
||
IProgress<DownloadProgress>? progress = null,
|
||
CancellationToken cancellationToken = default)
|
||
{
|
||
if (string.IsNullOrEmpty(yearMonth))
|
||
{
|
||
throw new ArgumentException("请选择月份", nameof(yearMonth));
|
||
}
|
||
|
||
var tempDir = CreateTempDirectory();
|
||
try
|
||
{
|
||
_logService?.Info($"开始下载 {yearMonth} 月份照片");
|
||
|
||
var config = _configService.LoadConfig();
|
||
var concurrency = config?.ImageDownloadConcurrency ?? DefaultConcurrency;
|
||
|
||
// 1. 获取月份图片列表
|
||
ReportDownloadProgress(progress, 0, 0, "正在获取图片列表...");
|
||
var result = await _apiService.GetMonthImagesAsync(yearMonth);
|
||
if (!result.Success || result.Data == null || result.Data.Count == 0)
|
||
{
|
||
throw new InvalidOperationException($"没有找到 {yearMonth} 月份的图片");
|
||
}
|
||
|
||
var monthImages = result.Data;
|
||
var totalImages = monthImages.Sum(m => m.ImageUrls.Count);
|
||
ReportDownloadProgress(progress, totalImages, 0, $"共 {totalImages} 张图片");
|
||
|
||
// 2. 按目录结构下载图片
|
||
var downloadedCount = 0;
|
||
foreach (var record in monthImages)
|
||
{
|
||
cancellationToken.ThrowIfCancellationRequested();
|
||
|
||
// 创建目录结构
|
||
var directories = CreateDirectoryStructure(tempDir, yearMonth, record);
|
||
|
||
// 下载图片到各个目录
|
||
foreach (var imageUrl in record.ImageUrls)
|
||
{
|
||
cancellationToken.ThrowIfCancellationRequested();
|
||
|
||
var imageData = await _imageService.DownloadImageAsync(imageUrl, cancellationToken);
|
||
if (imageData != null)
|
||
{
|
||
var fileName = GetFileNameFromUrl(imageUrl);
|
||
|
||
// 保存到各个分类目录
|
||
foreach (var dir in directories)
|
||
{
|
||
var filePath = Path.Combine(dir, fileName);
|
||
filePath = EnsureUniqueFilePath(filePath);
|
||
await File.WriteAllBytesAsync(filePath, imageData, cancellationToken);
|
||
}
|
||
}
|
||
|
||
downloadedCount++;
|
||
ReportDownloadProgress(progress, totalImages, downloadedCount, $"正在下载 {downloadedCount}/{totalImages}...");
|
||
}
|
||
}
|
||
|
||
// 3. 打包成 ZIP
|
||
ReportDownloadProgress(progress, totalImages, downloadedCount, "正在打包 ZIP...");
|
||
|
||
// 确保输出目录存在
|
||
var outputDir = Path.GetDirectoryName(outputPath);
|
||
if (!string.IsNullOrEmpty(outputDir) && !Directory.Exists(outputDir))
|
||
{
|
||
Directory.CreateDirectory(outputDir);
|
||
}
|
||
|
||
// 删除已存在的文件
|
||
if (File.Exists(outputPath))
|
||
{
|
||
File.Delete(outputPath);
|
||
}
|
||
|
||
ZipFile.CreateFromDirectory(tempDir, outputPath, CompressionLevel.Optimal, false);
|
||
|
||
ReportDownloadProgress(progress, totalImages, downloadedCount, "下载完成");
|
||
_logService?.Info($"照片 ZIP 下载完成: {outputPath}, 共 {downloadedCount} 张图片");
|
||
}
|
||
finally
|
||
{
|
||
CleanupTempDirectory(tempDir);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建目录结构并返回所有目录路径
|
||
/// 目录结构:/workfiles/{yyyyMM}/{yyyyMMdd}/当日照片/、参与人员/{人员姓名}/、工作内容/{工作内容}/、部门/{部门名称}/
|
||
/// </summary>
|
||
private List<string> CreateDirectoryStructure(string baseDir, string yearMonth, MonthImageDto record)
|
||
{
|
||
var directories = new List<string>();
|
||
var yyyyMM = yearMonth.Replace("-", "");
|
||
var yyyyMMdd = record.RecordDate?.ToString("yyyyMMdd") ?? yyyyMM + "01";
|
||
|
||
var basePath = Path.Combine(baseDir, "workfiles", yyyyMM, yyyyMMdd);
|
||
|
||
// 1. 当日照片目录
|
||
var dailyDir = Path.Combine(basePath, "当日照片");
|
||
EnsureDirectoryExists(dailyDir);
|
||
directories.Add(dailyDir);
|
||
|
||
// 2. 参与人员目录
|
||
foreach (var worker in record.Workers.Where(w => !string.IsNullOrEmpty(w)))
|
||
{
|
||
var workerDir = Path.Combine(basePath, "参与人员", SanitizeFileName(worker));
|
||
EnsureDirectoryExists(workerDir);
|
||
directories.Add(workerDir);
|
||
}
|
||
|
||
// 3. 工作内容目录
|
||
if (!string.IsNullOrEmpty(record.Content))
|
||
{
|
||
var contentDir = Path.Combine(basePath, "工作内容", SanitizeFileName(record.Content));
|
||
EnsureDirectoryExists(contentDir);
|
||
directories.Add(contentDir);
|
||
}
|
||
|
||
// 4. 部门目录
|
||
if (!string.IsNullOrEmpty(record.DeptName))
|
||
{
|
||
var deptDir = Path.Combine(basePath, "部门", SanitizeFileName(record.DeptName));
|
||
EnsureDirectoryExists(deptDir);
|
||
directories.Add(deptDir);
|
||
}
|
||
|
||
return directories;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 辅助方法
|
||
|
||
/// <summary>
|
||
/// 创建临时目录
|
||
/// </summary>
|
||
private string CreateTempDirectory()
|
||
{
|
||
var tempDir = Path.Combine(Path.GetTempPath(), "WorkCameraExport", $"export_{Guid.NewGuid():N}");
|
||
Directory.CreateDirectory(tempDir);
|
||
return tempDir;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清理临时目录
|
||
/// </summary>
|
||
private void CleanupTempDirectory(string tempDir)
|
||
{
|
||
try
|
||
{
|
||
var config = _configService.LoadConfig();
|
||
if (config?.AutoCleanTempFiles != false && Directory.Exists(tempDir))
|
||
{
|
||
Directory.Delete(tempDir, true);
|
||
_logService?.Info($"已清理临时目录: {tempDir}");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logService?.Warn($"清理临时目录失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 确保目录存在
|
||
/// </summary>
|
||
private void EnsureDirectoryExists(string path)
|
||
{
|
||
if (!Directory.Exists(path))
|
||
{
|
||
Directory.CreateDirectory(path);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从 URL 提取文件名
|
||
/// </summary>
|
||
private string GetFileNameFromUrl(string url)
|
||
{
|
||
try
|
||
{
|
||
var uri = new Uri(url);
|
||
var fileName = Path.GetFileName(uri.LocalPath);
|
||
if (!string.IsNullOrWhiteSpace(fileName) && fileName.Contains('.'))
|
||
{
|
||
return SanitizeFileName(fileName);
|
||
}
|
||
}
|
||
catch { }
|
||
|
||
return $"{Guid.NewGuid():N}.jpg";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清理文件名中的非法字符
|
||
/// </summary>
|
||
private string SanitizeFileName(string fileName)
|
||
{
|
||
var invalidChars = Path.GetInvalidFileNameChars();
|
||
var sanitized = fileName;
|
||
foreach (var c in invalidChars)
|
||
{
|
||
sanitized = sanitized.Replace(c, '_');
|
||
}
|
||
// 限制长度
|
||
if (sanitized.Length > 50)
|
||
{
|
||
var ext = Path.GetExtension(sanitized);
|
||
sanitized = sanitized.Substring(0, 50 - ext.Length) + ext;
|
||
}
|
||
return sanitized;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 确保文件路径唯一
|
||
/// </summary>
|
||
private string EnsureUniqueFilePath(string filePath)
|
||
{
|
||
if (!File.Exists(filePath))
|
||
{
|
||
return filePath;
|
||
}
|
||
|
||
var directory = Path.GetDirectoryName(filePath) ?? "";
|
||
var fileNameWithoutExt = Path.GetFileNameWithoutExtension(filePath);
|
||
var extension = Path.GetExtension(filePath);
|
||
var counter = 1;
|
||
|
||
string newPath;
|
||
do
|
||
{
|
||
newPath = Path.Combine(directory, $"{fileNameWithoutExt}_{counter}{extension}");
|
||
counter++;
|
||
} while (File.Exists(newPath));
|
||
|
||
return newPath;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 报告导出进度
|
||
/// </summary>
|
||
private void ReportProgress(
|
||
IProgress<ExportProgress>? progress,
|
||
int totalRecords,
|
||
int processedRecords,
|
||
int totalImages,
|
||
int downloadedImages,
|
||
string status)
|
||
{
|
||
progress?.Report(new ExportProgress
|
||
{
|
||
TotalRecords = totalRecords,
|
||
ProcessedRecords = processedRecords,
|
||
TotalImages = totalImages,
|
||
DownloadedImages = downloadedImages,
|
||
Status = status
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 报告下载进度
|
||
/// </summary>
|
||
private void ReportDownloadProgress(
|
||
IProgress<DownloadProgress>? progress,
|
||
int totalImages,
|
||
int downloadedImages,
|
||
string status)
|
||
{
|
||
progress?.Report(new DownloadProgress
|
||
{
|
||
TotalImages = totalImages,
|
||
DownloadedImages = downloadedImages,
|
||
Status = status
|
||
});
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
}
|