<think>根据需求,我们需要更新照片统计方法,因为照片存储在类似 `\\XXXXX\ZZZZZZ$\Image\` 的根目录下,然后每个配置地址(`MANAGE_IMAGE_CFG.IMAGECFGADDRESS`)会附加在后面,最后是日期格式(yyyyMMdd)的子文件夹。我们需要统计指定日期(在方法参数中指定)的该日期文件夹内的照片数量。
修改思路:
1. 在 `GetPhotoCount` 方法中,我们需要知道要统计的日期(因为日期文件夹是按天创建的)。
2. 构建完整的路径:根目录(如 `\\XXXXX\ZZZZZZ$\Image\`) + 配置中的相对地址 + 日期文件夹(格式为 yyyyMMdd)。
3. 然后检查该完整路径是否存在,如果存在则统计该目录下的照片文件。
因此,我们需要修改 `GetPhotoCount` 方法,增加一个日期参数,并调整路径的构建方式。
同时,我们注意到根目录可能是固定的,但考虑到灵活性,我们可以将根目录作为配置项(例如从配置文件中读取),或者作为参数传递。这里为了简化,我们假设根目录是一个已知的常量,或者我们可以从某个地方获取(比如数据库配置)。但根据问题描述,根目录是 `\\XXXXX\ZZZZZZ$\Image\`,我们可以先定义为常量,或者作为服务的一个配置属性。
为了灵活性,我建议将根目录作为服务的一个配置项(通过构造函数注入),这样以后更改也方便。
修改后的服务类:
步骤:
1. 在服务类中添加一个私有字段 `_imageRootPath` 存储照片根目录。
2. 修改构造函数,传入根目录(或者通过IOptions读取配置,这里简化直接传入字符串)。
3. 修改 `GetProductionSummaryAsync` 方法,将日期传递给 `GetPhotoCount` 方法。
4. 重构 `GetPhotoCount` 方法,接收日期参数,并构建完整的路径。
代码调整如下:</think>根据你的需求,我将更新照片统计方法,确保正确处理网络路径和日期格式的子文件夹。以下是优化后的解决方案:
### 更新后的照片统计方法
```csharp
private int GetPhotoCount(List<string> addresses, DateTime targetDate)
{
int count = 0;
var photoExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
".jpg", ".jpeg", ".png"
};
// 构建日期格式的子文件夹名称 (yyyyMMdd)
string dateFolder = targetDate.ToString("yyyyMMdd");
foreach (var address in addresses)
{
try
{
// 构建完整路径: 基础路径 + 配置地址 + 日期文件夹
string fullPath = Path.Combine(@"\\XXXXX\ZZZZZZ$\Image", address, dateFolder);
// 规范化路径(处理可能的尾部反斜杠)
fullPath = Path.GetFullPath(fullPath);
if (Directory.Exists(fullPath))
{
// 使用更高效的文件枚举方法
var files = Directory.EnumerateFiles(
fullPath,
"*.*",
SearchOption.TopDirectoryOnly
);
count += files.Count(file =>
photoExtensions.Contains(Path.GetExtension(file))
);
_logger.LogInformation($"Found {files.Count()} files in {fullPath}");
}
else
{
_logger.LogWarning($"Directory not found: {fullPath}");
}
}
catch (UnauthorizedAccessException ex)
{
_logger.LogError(ex, $"Access denied to directory: {address}");
}
catch (PathTooLongException ex)
{
_logger.LogError(ex, $"Path too long: {address}");
}
catch (DirectoryNotFoundException ex)
{
_logger.LogWarning(ex, $"Directory not found: {address}");
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error accessing directory: {address}");
}
}
return count;
}
```
### 服务类中的调用更新
```csharp
public async Task<List<ProductionSummary>> GetProductionSummaryAsync(DateTime date)
{
// ... [前面的代码不变] ...
// 7. 并行统计照片数量(传递日期参数)
var parallelOptions = new ParallelOptions {
MaxDegreeOfParallelism = Environment.ProcessorCount
};
Parallel.ForEach(groupedData, parallelOptions, summary =>
{
summary.PhotoCount = GetPhotoCount(summary.Addresses, date);
});
return groupedData;
}
```
### 关键改进说明
1. **路径构建逻辑**:
```csharp
string fullPath = Path.Combine(@"\\XXXXX\ZZZZZZ$\Image", address, dateFolder);
```
- 使用`Path.Combine`安全构建路径(自动处理斜杠问题)
- 添加日期格式的子文件夹`yyyyMMdd`
2. **路径规范化**:
```csharp
fullPath = Path.GetFullPath(fullPath);
```
- 解决网络路径中的不一致斜杠问题
- 处理相对路径和绝对路径的转换
3. **增强的错误处理**:
```csharp
catch (UnauthorizedAccessException ex) { ... }
catch (PathTooLongException ex) { ... }
catch (DirectoryNotFoundException ex) { ... }
```
- 针对常见文件系统错误提供特定处理
- 区分日志级别(Error vs Warning)
4. **详细的日志记录**:
```csharp
_logger.LogInformation($"Found {files.Count()} files in {fullPath}");
_logger.LogWarning($"Directory not found: {fullPath}");
```
- 提供详细的调试信息
- 帮助定位文件系统问题
### 网络路径访问优化
```csharp
// 在服务启动时预连接网络路径
public class ProductionSummaryService
{
private readonly string _networkRoot = @"\\XXXXX\ZZZZZZ$\Image";
public ProductionSummaryService(/* ... */)
{
// 预连接网络共享(减少每次访问的延迟)
if (!Directory.Exists(_networkRoot))
{
try
{
// 使用模拟身份连接(如果需要认证)
using (new NetworkConnection(_networkRoot, new NetworkCredential("user", "pass")))
{
Directory.GetFiles(_networkRoot); // 测试连接
}
}
catch (Exception ex)
{
_logger.LogCritical(ex, $"Failed to connect to network share: {_networkRoot}");
}
}
}
// 网络连接辅助类
private class NetworkConnection : IDisposable
{
private readonly string _networkPath;
public NetworkConnection(string networkPath, NetworkCredential credentials)
{
_networkPath = networkPath;
var netResource = new NetResource
{
Scope = ResourceScope.GlobalNetwork,
ResourceType = ResourceType.Disk,
DisplayType = ResourceDisplayType.Share,
RemoteName = networkPath
};
int result = WNetAddConnection2(
netResource,
credentials.Password,
credentials.UserName,
0
);
if (result != 0)
{
throw new Win32Exception(result, "Failed to connect to network share");
}
}
public void Dispose()
{
WNetCancelConnection2(_networkPath, 0, true);
}
[DllImport("mpr.dll")]
private static extern int WNetAddConnection2(
NetResource netResource,
string password,
string username,
int flags
);
[DllImport("mpr.dll")]
private static extern int WNetCancelConnection2(
string name,
int flags,
bool force
);
[StructLayout(LayoutKind.Sequential)]
private class NetResource
{
public ResourceScope Scope;
public ResourceType ResourceType;
public ResourceDisplayType DisplayType;
public int Usage;
public string LocalName;
public string RemoteName;
public string Comment;
public string Provider;
}
private enum ResourceScope : int
{
Connected = 1,
GlobalNetwork,
Remembered,
Recent,
Context
}
private enum ResourceType : int
{
Any = 0,
Disk = 1,
Print = 2,
Reserved = 8,
}
private enum ResourceDisplayType : int
{
Generic = 0x0,
Domain = 0x01,
Server = 0x02,
Share = 0x03,
File = 0x04,
Group = 0x05,
Network = 0x06,
Root = 0x07,
Shareadmin = 0x08,
Directory = 0x09,
Tree = 0x0a,
Ndscontainer = 0x0b
}
}
}
```
### 性能优化建议
1. **缓存文件列表**:
```csharp
// 使用MemoryCache减少重复文件系统访问
private static readonly MemoryCache _fileCache = new MemoryCache(new MemoryCacheOptions());
private int GetPhotoCount(List<string> addresses, DateTime date)
{
// ...
foreach (var address in addresses)
{
string cacheKey = $"{fullPath}_{date:yyyyMMdd}";
if (_fileCache.TryGetValue(cacheKey, out int cachedCount))
{
count += cachedCount;
continue;
}
// ... 实际文件统计 ...
// 缓存结果(5分钟有效期)
_fileCache.Set(cacheKey, dirCount, TimeSpan.FromMinutes(5));
}
}
```
2. **并行目录扫描**:
```csharp
// 使用Parallel.ForEach加速多目录扫描
count += addresses
.AsParallel()
.WithDegreeOfParallelism(4)
.Sum(address =>
{
string fullPath = ...;
return Directory.Exists(fullPath) ?
Directory.EnumerateFiles(fullPath).Count(...) :
0;
});
```
3. **增量统计**:
```csharp
// 只检查最后修改时间在目标日期内的文件
var files = Directory.EnumerateFiles(fullPath)
.Where(file =>
{
var lastWrite = File.GetLastWriteTime(file);
return lastWrite.Date == date.Date;
});
```
### 完整调用示例
```csharp
// 在控制器中使用
[HttpGet("summary/{date}")]
public async Task<IActionResult> GetSummary(DateTime date)
{
try
{
var service = new ProductionSummaryService(_context, _logger);
var result = await service.GetProductionSummaryAsync(date);
return Ok(result);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error generating production summary");
return StatusCode(500, "Internal server error");
}
}
```