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 { /// /// CSV服务 ///导出CSV文件 ///读取CSV 文件 /// public class CsvServices:BindableBase { public ILogService LogService { get; } public IFreeSql FreeSql { get; } public CsvServices(ILogService logService, IFreeSql freeSql) { LogService = logService; FreeSql = freeSql; RawProUseCsvPath = GetConfiguredOutputDirectory(); } /// /// 导出Csv目录 /// private string RawProUseCsvPath { get; set; } /// /// 从 App.config 读取导出目录(key: RawProUseCsvPath)。 /// 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; } /// /// 导出单条记录为 CSV 文件,文件名为记录的 InBagCode(自动追加 .csv 扩展名)。 /// 使用 App.config 中的 RawProUseCsvPath 作为导出目录,目录不存在会自动创建。 /// 失败时记录错误日志并返回空字符串。 /// /// 要导出的数据项,类型为 RawProUserCsvDto(InBagCode 不能为空) /// 若目标文件已存在,是否允许覆盖 /// 成功:返回导出后的完整文件路径;失败:返回空字符串 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(); csv.WriteHeader(); 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; } } /// /// 异步导出单条记录(包装线程池执行)。 /// public Task ExportSingleAsync(RawProUserCsvDto item, bool overwrite = false) => Task.Run(() => ExportSingle(item, overwrite)); /// /// 批量导出:将集合中的每一条记录分别导出为以 InBagCode 命名的 CSV 文件。 /// 成功项返回其路径,失败项返回空字符串(并已记录日志)。 /// public IEnumerable ExportMany(IEnumerable items, bool overwrite = false) { if (items is null) throw new ArgumentNullException(nameof(items)); var results = new List(); foreach (var it in items) { results.Add(ExportSingle(it, overwrite)); } return results; } /// /// 异步批量导出(并行度受限于线程池;如需更精细控制,可改为并行批处理)。 /// public async Task> ExportManyAsync(IEnumerable 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(); } /// /// 清理文件名中的非法字符。 /// 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(); } } }