Files
FATrace/FATrace.WPLApp/Services/CsvServices.cs
2025-11-26 16:46:48 +08:00

164 lines
6.1 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 System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Globalization;
using System.IO;
using CsvHelper;
using CsvHelper.Configuration;
using FATrace.WPLApp.ModelDto;
using Prism.Mvvm;
using FATrace.WPLApp.CsvModel;
using FATrace.Com;
namespace FATrace.WPLApp.Services
{
/// <summary>
/// CSV服务
///导出CSV文件
///读取CSV 文件
/// </summary>
public class CsvServices:BindableBase
{
public ILogService LogService { get; }
public IFreeSql FreeSql { get; }
public CsvServices(ILogService logService, IFreeSql freeSql)
{
LogService = logService;
FreeSql = freeSql;
RawProUseCsvPath = GetConfiguredOutputDirectory();
}
/// <summary>
/// 导出Csv目录
/// </summary>
private string RawProUseCsvPath { get; set; }
/// <summary>
/// 从 App.config 读取导出目录key: RawProUseCsvPath
/// </summary>
private string GetConfiguredOutputDirectory()
{
var dir = ConfigHelper.GetValue("RawProUseCsvPath");
// 支持环境变量(如 %USERPROFILE%
dir = Environment.ExpandEnvironmentVariables(dir ?? string.Empty).Trim();
if (string.IsNullOrWhiteSpace(dir))
throw new InvalidOperationException("配置项 RawProUseCsvPath 为空");
return dir;
}
/// <summary>
/// 导出单条记录为 CSV 文件,文件名为记录的 InBagCode自动追加 .csv 扩展名)。
/// 使用 App.config 中的 RawProUseCsvPath 作为导出目录,目录不存在会自动创建。
/// 失败时记录错误日志并返回空字符串。
/// </summary>
/// <param name="item">要导出的数据项,类型为 RawProUserCsvDtoInBagCode 不能为空)</param>
/// <param name="overwrite">若目标文件已存在,是否允许覆盖</param>
/// <returns>成功:返回导出后的完整文件路径;失败:返回空字符串</returns>
public string ExportSingle(RawProUserCsvDto item, bool overwrite = false)
{
try
{
if (item is null) throw new ArgumentNullException(nameof(item));
// 线程安全:使用局部缓存目录变量,必要时从配置读取一次并回写字段。
var outputDir = RawProUseCsvPath;
if (string.IsNullOrWhiteSpace(outputDir))
{
outputDir = GetConfiguredOutputDirectory();
RawProUseCsvPath = outputDir;
}
var inBagCode = (item.InBagCode ?? string.Empty).Trim();
if (string.IsNullOrWhiteSpace(inBagCode))
throw new ArgumentException("InBagCode 不能为空", nameof(item.InBagCode));
// 目录确保存在
Directory.CreateDirectory(outputDir);
var safeName = SanitizeFileName(inBagCode);
var filePath = Path.Combine(outputDir, safeName + ".csv");
if (File.Exists(filePath) && !overwrite)
{
throw new IOException($"文件已存在且不允许覆盖: {filePath}");
}
// 使用 UTF8 BOM便于 Excel 正确识别中文
using var writer = new StreamWriter(filePath, false, new UTF8Encoding(encoderShouldEmitUTF8Identifier: true));
using var csv = new CsvWriter(writer, CultureInfo.InvariantCulture);
// 固定列顺序映射,并设置日期格式
csv.Context.RegisterClassMap<RawProUserCsvDtoMap>();
csv.WriteHeader<RawProUserCsvDto>();
csv.NextRecord();
csv.WriteRecord(item);
csv.NextRecord();
return filePath;
}
catch (Exception ex)
{
var code = item?.InBagCode ?? string.Empty;
var outputDir = RawProUseCsvPath;
LogService.Error($"导出CSV失败目录={outputDir}, InBagCode={code}", ex);
return string.Empty;
}
}
/// <summary>
/// 异步导出单条记录(包装线程池执行)。
/// </summary>
public Task<string> ExportSingleAsync(RawProUserCsvDto item, bool overwrite = false)
=> Task.Run(() => ExportSingle(item, overwrite));
/// <summary>
/// 批量导出:将集合中的每一条记录分别导出为以 InBagCode 命名的 CSV 文件。
/// 成功项返回其路径,失败项返回空字符串(并已记录日志)。
/// </summary>
public IEnumerable<string> ExportMany(IEnumerable<RawProUserCsvDto> items, bool overwrite = false)
{
if (items is null) throw new ArgumentNullException(nameof(items));
var results = new List<string>();
foreach (var it in items)
{
results.Add(ExportSingle(it, overwrite));
}
return results;
}
/// <summary>
/// 异步批量导出(并行度受限于线程池;如需更精细控制,可改为并行批处理)。
/// </summary>
public async Task<List<string>> ExportManyAsync(IEnumerable<RawProUserCsvDto> items, bool overwrite = false)
{
if (items is null) throw new ArgumentNullException(nameof(items));
var list = items.ToList();
var tasks = list.Select(it => Task.Run(() => ExportSingle(it, overwrite)));
var paths = await Task.WhenAll(tasks);
return paths.ToList();
}
/// <summary>
/// 清理文件名中的非法字符。
/// </summary>
private static string SanitizeFileName(string name)
{
var invalid = Path.GetInvalidFileNameChars();
var sb = new StringBuilder(name.Length);
foreach (var ch in name)
{
sb.Append(invalid.Contains(ch) ? '_' : ch);
}
return sb.ToString();
}
}
}