using AIK.Common; using AIK.Common.WebHelper; using AIK.Models; using AIK.Service.IService; using Microsoft.Extensions.Logging; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Media.Imaging; namespace AIK.Service.Service { public class FileBinService : IFileCacheService { private readonly string _cacheBaseDirectory; private readonly IHttpClientService _httpClient; private readonly IConfigurationService _configurationService; private readonly ILogger _logger; private string GetCacheFilePath(string cacheKey) => Path.Combine(_cacheBaseDirectory, $"{cacheKey}.bin"); public FileBinService(IConfigurationService configurationService, IHttpClientService httpClient, ILogger logger) { _configurationService = configurationService; _httpClient = httpClient; _logger = logger; // 设置缓存目录,例如在用户本地应用数据文件夹下 _cacheBaseDirectory = CommonString.BinCacheDirectory; Directory.CreateDirectory(_cacheBaseDirectory); // 确保目录存在 } public async Task ClearAllCacheAsync() { await Task.Run(() => { try { var directory = new DirectoryInfo(_cacheBaseDirectory); if (directory.Exists) { directory.Delete(true); Directory.CreateDirectory(_cacheBaseDirectory); _logger.LogInformation("清空所有缓存完成"); } } catch (Exception ex) { _logger.LogError(ex, "清空所有缓存失败"); } }); } public async Task ClearExpiredCacheAsync() { await Task.Run(() => { try { var directory = new DirectoryInfo(_cacheBaseDirectory); if (!directory.Exists) return; int deletedCount = 0; foreach (var file in directory.GetFiles("*.bin")) { try { file.Delete(); deletedCount++; } catch (Exception ex) { _logger.LogWarning(ex, "删除缓存文件失败: {FilePath}", file.FullName); } } _logger.LogInformation("清理缓存完成,删除文件数: {DeletedCount}", deletedCount); } catch (Exception ex) { _logger.LogError(ex, "清理缓存失败"); } }); } public async Task GetFileByteAsync(string fileUrl, string cacheKey = null, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(fileUrl)) return GetEmptyByte(); // 生成缓存键,如果未提供则使用URL的MD5值 var key = cacheKey ?? GenerateCacheKey(fileUrl); if (fileUrl.Contains("http")) { var urifileName = UrlHelper.GetEncodedFileNameFromUrl(fileUrl); var realfileName = Uri.UnescapeDataString(urifileName); key = GenerateCacheKey(realfileName); } // 2. 检查磁盘缓存 string filePath = GetCacheFilePath(key); if (File.Exists(filePath)) { //_logger.LogDebug("磁盘缓存命中: {FilePath}", filePath); // 从磁盘加载并放入内存 var filebyte = await LoadFileFromDiskAsync(filePath); return filebyte; } // 3. 从网络下载 try { var fullImgUrl = _configurationService.ApiSettings.FileUrl + fileUrl; if (fileUrl.Contains("http")) { fullImgUrl = fileUrl; } _logger.LogInformation("开始下载bin文件: {Url}", fullImgUrl); // Flurl.Http 字节下载(连接级显式 TLS 1.2;非 2xx 会抛异常,由外层 catch 统一处理) byte[] fileBytes = await _httpClient.GetByteArrayWithTimeoutAsync(fullImgUrl); if (fileBytes == null || fileBytes.Length == 0) { _logger.LogWarning("文件数据为空: {Url}", fullImgUrl); return GetEmptyByte(); } // 保存到磁盘缓存 await SavefileToDiskAsync(fileBytes, filePath); return fileBytes; } catch (Exception ex) { _logger.LogError(ex, "下载文件失败: {fileUrl}", filePath); } return GetEmptyByte(); } #region 辅助方法 private byte[] GetEmptyByte() { return Array.Empty(); } /// /// 保存字节数组到磁盘 /// private async Task SavefileToDiskAsync(byte[] fileBytes, string filePath) { try { // 使用临时文件避免写入过程中出错 var tempFilePath = filePath + ".tmp"; File.WriteAllBytes(tempFilePath, fileBytes); // 原子性操作:重命名临时文件 if (File.Exists(filePath)) { File.Delete(filePath); } File.Move(tempFilePath, filePath); _logger.LogDebug("文件保存成功: {FilePath}, 大小: {Size}字节", filePath, fileBytes.Length); } catch (Exception ex) { _logger.LogError(ex, "保存文件到磁盘失败: {FilePath}", filePath); // 清理临时文件 var tempFilePath = filePath + ".tmp"; if (File.Exists(tempFilePath)) { File.Delete(tempFilePath); } } } /// /// 优化的磁盘加载方法 /// private async Task LoadFileFromDiskAsync(string filePath) { if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath)) { return GetEmptyByte(); } try { // 直接读取文件字节数组 byte[] fileBytes = File.ReadAllBytes(filePath); if (fileBytes == null || fileBytes.Length == 0) { _logger.LogWarning("磁盘缓存文件为空: {FilePath}", filePath); // 删除损坏的文件 File.Delete(filePath); return GetEmptyByte(); } return fileBytes; } catch (Exception ex) { _logger.LogError(ex, "从磁盘加载文件失败: {FilePath}", filePath); return GetEmptyByte(); } } private string GenerateCacheKey(string url) { using (var md5 = System.Security.Cryptography.MD5.Create()) { byte[] inputBytes = System.Text.Encoding.UTF8.GetBytes(url); byte[] hashBytes = md5.ComputeHash(inputBytes); return Convert.ToBase64String(hashBytes).Replace("/", "_").Replace("+", "-").Substring(0, 10); } } #endregion #region private async Task UpdateOrAddBinFile(List vehKeys) { var binFilePathList = new ConcurrentDictionary(); foreach (var item in vehKeys) { var tempArray = item.sourceUrl.Split(','); foreach (var tempitem in tempArray) { var filename = UrlHelper.GetEncodedFileNameFromUrl(tempitem); binFilePathList.GetOrAdd(filename, tempitem); } } } public Task<(bool success, string localFilePath)> DownloadFileWithProgressAsync(string fileUrl, string cacheKey, IProgress progress = null, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } #endregion } }