需求说明
根据日期查询出这个时间段内的现场图片,然后选择现场导出。导出的图片按照一个现场一个文件夹,并且每个现场下按天拆分文件夹。需要兼容文件存储和文件存储的方式。
关键效果图
上传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.Id
select 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.Id
select 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; }
}