164 lines
6.1 KiB
C#
164 lines
6.1 KiB
C#
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">要导出的数据项,类型为 RawProUserCsvDto(InBagCode 不能为空)</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();
|
||
}
|
||
|
||
|
||
}
|
||
}
|