.net8导出影像图片按现场及天拆分
需求说明
根据日期查询出这个时间段内的现场图片,然后选择现场导出。导出的图片按照一个现场一个文件夹,并且每个现场下按天拆分文件夹。需要兼容文件存储和文件存储的方式。
关键效果图
上传Controller关键部分
Controller部分
public class UploadController{private readonly IWebHostEnvironment _env;readonly FileService _fileservice;public UploadController(FileService fileservice,IWebHostEnvironment env){_fileservice = fileservice;_env = env;}/// <summary>/// 现场图片下载/// </summary>/// <returns></returns>[HttpPost]public async Task<Result> SceneImageDownload(JObject obj){var exportData = obj.GetObject<ScenePhoto_Export>("exportData");return await _fileservice.SceneImageDownload(exportData, _env.ContentRootPath);}
}
Servcie部分
public async Task<Result> SceneImageDownload(ScenePhoto_Export exportData, string outputPath = "")
{switch (StorageConfig){case StorageConfig.Cos: //cos存储case StorageConfig.Oss: //oss存储case StorageConfig.Obs: //obs存储 当前年月日字符串var tziprelativePath = $"ExportFiles/{DateTime.Now.Year}/{DateTime.Now.Month}/{DateTime.Now.Day}/"; //相对路径string tzipOutputRoot = Path.Combine(outputPath, tziprelativePath.Replace("/", Path.DirectorySeparatorChar.ToString()));// 确保目录存在if (!Directory.Exists(tzipOutputRoot)){Directory.CreateDirectory(tzipOutputRoot);}string texportFolderName = $"{exportData.Title}";string ttempExportPath = Path.Combine(Path.GetTempPath(), texportFolderName);Directory.CreateDirectory(ttempExportPath);var failList = new List<string>();System.Net.WebClient mywebclient = new System.Net.WebClient();try{foreach (var scene in exportData.SceneList){string sceneFolder = Path.Combine(ttempExportPath, scene.Name);Directory.CreateDirectory(sceneFolder);foreach (var day in scene.DayList){string dayFolder = Path.Combine(sceneFolder, day.ForDate.ToString("yyyy-MM-dd"));Directory.CreateDirectory(dayFolder);foreach (var picGuid in day.PicList){// 查 PicRelation 获取远程文件信息var picData = await _dbContext.Set<PicRelation>().Where(p => p.Pic == picGuid || p.Pic == new Guid(picGuid).ToString()).Select(p => new{p.RemoteUrl,p.Format,}).FirstOrDefaultAsync();if (picData == null || string.IsNullOrEmpty(picData.RemoteUrl)){failList.Add($"未找到云存储图片或 RemoteUrl:{picGuid}");continue;}// 处理文件名,没有 FileName 就用 picGuid + 后缀string extension = string.IsNullOrWhiteSpace(picData.Format) ? ".jpg" : "." + picData.Format.Trim('.');string fileName = picGuid + extension;string destPath = Path.Combine(dayFolder, fileName);try{var bytes = await mywebclient.DownloadDataTaskAsync(picData.RemoteUrl);await System.IO.File.WriteAllBytesAsync(destPath, bytes);}catch (Exception ex){failList.Add($"下载云存储图片失败:{picGuid},错误:{ex.Message}");}}}}if (failList.Any()){string failLogPath = Path.Combine(ttempExportPath, "导出失败.txt");await System.IO.File.WriteAllLinesAsync(failLogPath, failList);}string zipFileName = $"{texportFolderName}.zip";string finalZipPath = Path.Combine(tzipOutputRoot, zipFileName);// 如果已存在,删除旧文件if (System.IO.File.Exists(finalZipPath)){System.IO.File.Delete(finalZipPath);}ZipFile.CreateFromDirectory(ttempExportPath, finalZipPath);var zipPath = $"{_configuration["Service_ApiUrl"]}/{tziprelativePath}{texportFolderName}.zip";return Result.NewSuccess(data: zipPath);}catch (Exception ex){return Result.NewError(message: "导出失败");}finally{if (Directory.Exists(ttempExportPath))Directory.Delete(ttempExportPath, true);}break;case StorageConfig.File: //文件存储var picGuids = exportData.SceneList.SelectMany(t => t.DayList.SelectMany(t => t.PicList)).Select(s => s).ToList();var tpicGuids = picGuids.Select(t => new Guid(t).ToString()).ToList();if (string.IsNullOrEmpty(AppStartingCache.FileStorage_BaseUrl)){bool.TryParse(_configuration["FileWebServer_Db:IsLocal"], out bool islocal);AppStartingCache.FileStorage_IsLocal = islocal;AppStartingCache.FileStorage_BaseUrl = $"http://{_configuration["FileWebServer_Db:ServerHost"]}:{_configuration["FileWebServer_Db:ServerPort"]}";}if (!AppStartingCache.FileStorage_IsLocal) //远程获取{string fullurl = $"{AppStartingCache.FileStorage_BaseUrl}/file/msupload/SceneImageDownload?";var rst = await HttpClientProxy_Mis.Post2Async<Result<string>>($"{fullurl}", para: new{exportData = exportData});return rst;}else{// 查询图片路径前缀(ServerUseFor = 0)string imageRoot = _dbContext.Set<ImageServerInfo>().Where(s => s.ServerUseFor == ServerUseFor.Pic).Select(s => s.PicRootPath).FirstOrDefault();// 查询压缩包输出根路径(ServerUseFor = 2)string zipOutputRoot = _dbContext.Set<ImageServerInfo>().Where(s => s.ServerUseFor == ServerUseFor.LargeFile).Select(s => s.PicRootPath).FirstOrDefault();var serverId = _dbContext.Set<ImageServerInfo>().Where(s => s.ServerUseFor == ServerUseFor.LargeFile).Select(s => s.Id).FirstOrDefault();if (string.IsNullOrEmpty(imageRoot) || string.IsNullOrEmpty(zipOutputRoot)){return Result.NewError(message: "图片路径前缀或导出目录未配置。");}//得到所有图片的地址及名称var temp = await _dbContext.Set<ImageInfo>().Where(a => picGuids.Contains(a.PicGuid)).Select(s => new{s.PicGuid,s.PicName,s.Format,s.RelativePath}).ToListAsync();if (temp.Count == 0){temp = await _dbContext.Set<ImageInfo>().Where(a => tpicGuids.Contains(a.PicGuid)).Select(s => new{s.PicGuid,s.PicName,s.Format,s.RelativePath}).ToListAsync();}if (temp.Count > 0)//表示有图片 {var imageInfoDict = temp.ToDictionary(x => x.PicGuid, x => x);// 3. 临时目录(系统临时目录)string exportFolderName = $"{exportData.Title}";string tempExportPath = Path.Combine(Path.GetTempPath(), exportFolderName);Directory.CreateDirectory(tempExportPath);try{foreach (var scene in exportData.SceneList){string sceneFolder = Path.Combine(tempExportPath, scene.Name);Directory.CreateDirectory(sceneFolder);foreach (var day in scene.DayList){string dayStr = day.ForDate.ToString("yyyy-MM-dd");string dayFolder = Path.Combine(sceneFolder, dayStr);Directory.CreateDirectory(dayFolder);foreach (var picGuid in day.PicList){if (!imageInfoDict.TryGetValue(picGuid, out var imageInfo))continue; // 没有查到图片信息,跳过string relativePath = imageInfo.RelativePath.Replace("/", Path.DirectorySeparatorChar.ToString());string extension = string.IsNullOrWhiteSpace(imageInfo.Format) ? ".jpg" : "." + imageInfo.Format.Trim('.');string sourcePath = Path.Combine(imageRoot, relativePath);string destPath = Path.Combine(dayFolder, imageInfo.PicName + extension);if (System.IO.File.Exists(sourcePath)){System.IO.File.Copy(sourcePath, destPath, true);}else{Console.WriteLine($"文件不存在:{sourcePath}");}}}}// 4. 生成 zip 文件到 ServerUseFor=1 路径下string zipFileName = $"{exportData.Title}.zip"; 当前年月日字符串var ziprelativePath = $"ExportFiles/{DateTime.Now.Year}/{DateTime.Now.Month}/{DateTime.Now.Day}/"; //相对路径// 构建完整导出路径string finalFolderPath = Path.Combine(zipOutputRoot, ziprelativePath.Replace("/", Path.DirectorySeparatorChar.ToString()));// 确保目录存在if (!Directory.Exists(finalFolderPath)){Directory.CreateDirectory(finalFolderPath);}string finalZipPath = Path.Combine(finalFolderPath, zipFileName);// 如果已存在,删除旧文件if (System.IO.File.Exists(finalZipPath)){System.IO.File.Delete(finalZipPath);}ZipFile.CreateFromDirectory(tempExportPath, finalZipPath);var guid = Guid.NewGuid().ToString("");string zipextension = Path.GetExtension(zipFileName)?.TrimStart('.') ?? "";await _dbContext.Set<ImageInfo>().AddAsync(new ImageInfo{PicName = zipFileName,Format = zipextension,RelativePath = ziprelativePath + exportData.Title + ".zip", // 存储图片相对路径ImageServerId = serverId, // 存储服务器编号PicGuid = guid,CreateOnYMD = DateTime.Now.DateTimeYMD_Int()});await _dbContext.SaveChangesAsync();var serverUrl = await _dbContext.Set<ImageServerInfo>().Where(a => a.Id == serverId).Select(a => a.ServerUrl).FirstOrDefaultAsync();var zipPath = $"{serverUrl}file/msupload/file_misimage?id={guid}";return Result.NewSuccess(data: zipPath);}catch (Exception ex){return Result.NewError(message: "导出失败");}finally{// 清理临时文件夹if (Directory.Exists(tempExportPath))Directory.Delete(tempExportPath, true);}}return Result.NewError(message: "暂无可导出图片");}break;}return Result.NewError(message: "导出失败");
}
文件存储controlelr关键部分
[HttpGet]public async Task<FileResult> File_Misimage(string id){var rst = await _fileservice.File_Misimage(id);if (!string.IsNullOrEmpty(rst.Format)){rst.Format = rst.Format.Contains('.') ? rst.Format.TrimStart('.') : rst.Format;return File(rst.Pic, MIMEHelper.GetMimeValue(rst.Format), fileDownloadName: $"{rst.FileName}.{rst.Format}");}else{var defaultPic = _defaultimg.DefaultFileStream();return File(defaultPic, MIMEHelper.GetMimeValue("png"), fileDownloadName: "default");}}
文件存储获取文件关键部分
public async Task<PicByteData> File_Misimage(string id){switch (StorageConfig){case StorageConfig.Cos:case StorageConfig.Oss:case StorageConfig.Obs:var tempid = new Guid(id).ToString();var picData = await _dbContext.Set<PicRelation>().Where(a => a.Pic == tempid).Select(a => new { a.RemoteUrl, a.Format }).FirstOrDefaultAsync();if (picData == null){picData = await _dbContext.Set<PicRelation>().Where(a => a.Pic == id).Select(a => new { a.RemoteUrl, a.Format }).FirstOrDefaultAsync();}if (picData != null){byte[] Bytes = null;using (WebClient mywebclient = new WebClient()){Bytes = mywebclient.DownloadData(picData.RemoteUrl);}return new PicByteData { Pic = Bytes, Format = picData.Format };}break;case StorageConfig.Sqlserver:var guid = new Guid(id);var tmp = await _dbContext.Set<PicStorage>().Where(a => a.Id == guid).Select(a => new { a.Pic, a.Format }).FirstOrDefaultAsync();if (tmp != null){return new PicByteData { Pic = tmp.Pic, Format = tmp.Format };}break;case StorageConfig.File:var tid = new Guid(id).ToString();if (string.IsNullOrEmpty(AppStartingCache.FileStorage_BaseUrl)){bool.TryParse(_configuration["FileWebServer_Db:IsLocal"], out bool islocal);AppStartingCache.FileStorage_IsLocal = islocal;AppStartingCache.FileStorage_BaseUrl = $"http://{_configuration["FileWebServer_Db:ServerHost"]}:{_configuration["FileWebServer_Db:ServerPort"]}";}if (!AppStartingCache.FileStorage_IsLocal) //远程获取{string fullurl = $"{AppStartingCache.FileStorage_BaseUrl}/file/msupload/file_misimage_byte?id={tid}";var ret = await HttpClientProxy_Mis.GetAsync<PicByteData>(fullurl);return ret;}else{var temp = await (from r in _dbContext.Set<ImageInfo>().Where(a => a.PicGuid == tid)join s in _dbContext.Set<ImageServerInfo>() on r.ImageServerId equals s.Idselect new{s.PicRootPath,r.RelativePath,r.Format,r.PicName}).FirstOrDefaultAsync();if (temp == null){temp = await (from r in _dbContext.Set<ImageInfo>().Where(a => a.PicGuid == id)join s in _dbContext.Set<ImageServerInfo>() on r.ImageServerId equals s.Idselect new{s.PicRootPath,r.RelativePath,r.Format,r.PicName}).FirstOrDefaultAsync();}if (temp != null){var fullpath = temp.PicRootPath + temp.RelativePath;byte[] bytes = null;using (var picstream = System.IO.File.OpenRead(fullpath)){bytes = picstream.ConvertStreamToBytes();picstream.Dispose();}return new PicByteData { Pic = bytes, Format = temp.Format, FileName = temp.PicName };}}break;default:break;}return new PicByteData();}
模型部分
public class PicByteData{public byte[] Pic { get; set; }public string Format { get; set; }/// <summary>/// 文件名称/// </summary>public string FileName { get; set; }}