第09章 - 异常处理体系

第09章 - 异常处理体系

9.1 异常体系概述

9.1.1 异常层次结构

OGU4Net定义了完整的异常类层次:

System.Exception
    └── OguException (OGU基础异常)
            ├── DataSourceException (数据源异常)
            ├── FormatParseException (格式解析异常)
            ├── EngineNotSupportedException (引擎不支持异常)
            ├── LayerValidationException (图层验证异常)
            └── TopologyException (拓扑异常)

9.1.2 异常类型说明

异常类型 使用场景 示例
OguException 基础异常,通用GIS错误 一般操作失败
DataSourceException 数据源访问问题 文件不存在、数据库连接失败
FormatParseException 数据格式解析问题 WKT解析失败、JSON格式错误
EngineNotSupportedException 引擎不支持 驱动不可用、格式不支持
LayerValidationException 图层数据验证失败 字段重复、属性不匹配
TopologyException 拓扑操作失败 几何无效、拓扑规则违反

9.2 OguException基类

9.2.1 类定义

namespace OpenGIS.Utils.Exception;

/// <summary>
/// OGU基础异常类
/// </summary>
public class OguException : System.Exception
{
    /// <summary>
    /// 错误代码
    /// </summary>
    public int ErrorCode { get; set; }
    
    /// <summary>
    /// 上下文信息
    /// </summary>
    public Dictionary<string, object> Context { get; set; }
    
    public OguException(string message) : base(message)
    {
        Context = new Dictionary<string, object>();
    }
    
    public OguException(string message, System.Exception innerException) 
        : base(message, innerException)
    {
        Context = new Dictionary<string, object>();
    }
    
    public OguException(string message, int errorCode) : base(message)
    {
        ErrorCode = errorCode;
        Context = new Dictionary<string, object>();
    }
}

9.2.2 使用上下文信息

try
{
    // 某些操作
    throw new OguException("操作失败", 1001)
    {
        Context =
        {
            ["FilePath"] = "data.shp",
            ["Operation"] = "Read",
            ["Timestamp"] = DateTime.Now
        }
    };
}
catch (OguException ex)
{
    Console.WriteLine($"错误: {ex.Message}");
    Console.WriteLine($"错误码: {ex.ErrorCode}");
    
    foreach (var (key, value) in ex.Context)
    {
        Console.WriteLine($"  {key}: {value}");
    }
}

9.3 具体异常类型

9.3.1 DataSourceException

数据源相关异常:

namespace OpenGIS.Utils.Exception;

public class DataSourceException : OguException
{
    public DataSourceException(string message) : base(message) { }
    
    public DataSourceException(string message, System.Exception innerException) 
        : base(message, innerException) { }
    
    public DataSourceException(string message, int errorCode) 
        : base(message, errorCode) { }
}

使用场景:

// 打开数据源失败
if (dataSource == null)
{
    throw new DataSourceException($"无法打开数据源: {path}")
    {
        Context = { ["Path"] = path }
    };
}

// 连接数据库失败
try
{
    // 连接PostGIS
}
catch (System.Exception ex)
{
    throw new DataSourceException("PostGIS连接失败", ex)
    {
        Context =
        {
            ["Host"] = host,
            ["Database"] = database
        }
    };
}

9.3.2 FormatParseException

格式解析异常:

namespace OpenGIS.Utils.Exception;

public class FormatParseException : OguException
{
    public FormatParseException(string message) : base(message) { }
    
    public FormatParseException(string message, System.Exception innerException) 
        : base(message, innerException) { }
    
    public FormatParseException(string message, int errorCode) 
        : base(message, errorCode) { }
}

使用场景:

// WKT解析失败
try
{
    var geom = Geometry.CreateFromWkt(wkt);
    if (geom == null)
    {
        throw new FormatParseException($"无效的WKT: {wkt}");
    }
}
catch (System.Exception ex) when (!(ex is FormatParseException))
{
    throw new FormatParseException($"WKT解析失败: {wkt}", ex);
}

// JSON解析失败
try
{
    var layer = JsonSerializer.Deserialize<OguLayer>(json);
}
catch (JsonException ex)
{
    throw new FormatParseException("JSON格式无效", ex);
}

9.3.3 EngineNotSupportedException

引擎不支持异常:

namespace OpenGIS.Utils.Exception;

public class EngineNotSupportedException : OguException
{
    public EngineNotSupportedException(string message) : base(message) { }
    
    public EngineNotSupportedException(string message, System.Exception innerException) 
        : base(message, innerException) { }
    
    public EngineNotSupportedException(string message, int errorCode) 
        : base(message, errorCode) { }
}

使用场景:

// 驱动不可用
if (!GdalConfiguration.IsDriverAvailable("FileGDB"))
{
    throw new EngineNotSupportedException("FileGDB驱动不可用")
    {
        Context =
        {
            ["Driver"] = "FileGDB",
            ["AvailableDrivers"] = string.Join(", ", 
                GdalConfiguration.GetSupportedDrivers().Take(10))
        }
    };
}

// 不支持的引擎类型
throw new EngineNotSupportedException($"不支持的引擎类型: {engineType}");

9.3.4 LayerValidationException

图层验证异常:

namespace OpenGIS.Utils.Exception;

public class LayerValidationException : OguException
{
    public LayerValidationException(string message) : base(message) { }
    
    public LayerValidationException(string message, System.Exception innerException) 
        : base(message, innerException) { }
    
    public LayerValidationException(string message, int errorCode) 
        : base(message, errorCode) { }
}

使用场景:

// 图层名称为空
if (string.IsNullOrWhiteSpace(layer.Name))
{
    throw new LayerValidationException("图层名称不能为空");
}

// 字段重复
if (Fields.Any(f => f.Name == field.Name))
{
    throw new LayerValidationException($"字段'{field.Name}'已存在");
}

// 属性不匹配
foreach (var fieldName in feature.Attributes.Keys)
{
    if (!fieldNameSet.Contains(fieldName))
    {
        throw new LayerValidationException(
            $"要素包含未定义的属性'{fieldName}'");
    }
}

9.3.5 TopologyException

拓扑异常:

namespace OpenGIS.Utils.Exception;

public class TopologyException : OguException
{
    public TopologyException(string message) : base(message) { }
    
    public TopologyException(string message, System.Exception innerException) 
        : base(message, innerException) { }
    
    public TopologyException(string message, int errorCode) 
        : base(message, errorCode) { }
}

使用场景:

// 几何无效
var result = GeometryUtil.IsValid(geom);
if (!result.IsValid)
{
    throw new TopologyException($"几何无效: {result.ErrorMessage}")
    {
        Context =
        {
            ["ErrorType"] = result.ErrorType,
            ["WKT"] = GeometryUtil.Geometry2Wkt(geom)
        }
    };
}

// 拓扑操作失败
try
{
    var intersection = geomA.Intersection(geomB);
}
catch (System.Exception ex)
{
    throw new TopologyException("交集运算失败", ex);
}

9.4 异常处理模式

9.4.1 基本异常处理

try
{
    var layer = OguLayerUtil.ReadLayer(DataFormatType.SHP, "data.shp");
    ProcessLayer(layer);
}
catch (DataSourceException ex)
{
    Console.Error.WriteLine($"数据源错误: {ex.Message}");
    LogError(ex);
}
catch (FormatParseException ex)
{
    Console.Error.WriteLine($"格式解析错误: {ex.Message}");
    LogError(ex);
}
catch (OguException ex)
{
    Console.Error.WriteLine($"GIS操作错误: {ex.Message}");
    LogError(ex);
}
catch (System.Exception ex)
{
    Console.Error.WriteLine($"未知错误: {ex.Message}");
    LogError(ex);
}

9.4.2 分层异常处理

// 服务层
public OguLayer ReadLayerSafe(string path)
{
    try
    {
        return OguLayerUtil.ReadLayer(DataFormatType.SHP, path);
    }
    catch (DataSourceException)
    {
        throw;  // 直接传递
    }
    catch (System.Exception ex)
    {
        // 包装为更具体的异常
        throw new DataSourceException($"读取图层失败: {path}", ex);
    }
}

// 控制器层
public IActionResult GetLayer(string path)
{
    try
    {
        var layer = ReadLayerSafe(path);
        return Ok(layer);
    }
    catch (DataSourceException ex)
    {
        return NotFound(new { error = ex.Message });
    }
    catch (OguException ex)
    {
        return BadRequest(new { error = ex.Message });
    }
}

9.4.3 使用异常过滤器

// C# 6+ 异常过滤器
try
{
    ProcessData(data);
}
catch (OguException ex) when (ex.ErrorCode == 1001)
{
    // 处理特定错误码
    HandleSpecificError(ex);
}
catch (OguException ex) when (ex.Context.ContainsKey("Recoverable"))
{
    // 可恢复的错误
    Retry();
}
catch (OguException ex)
{
    // 其他OGU异常
    throw;
}

9.4.4 异常重试模式

public OguLayer ReadLayerWithRetry(string path, int maxRetries = 3)
{
    int attempt = 0;
    
    while (true)
    {
        try
        {
            return OguLayerUtil.ReadLayer(DataFormatType.SHP, path);
        }
        catch (DataSourceException ex) when (attempt < maxRetries)
        {
            attempt++;
            Console.WriteLine($"读取失败,第{attempt}次重试...");
            Thread.Sleep(1000 * attempt);  // 递增延迟
        }
        catch (DataSourceException ex)
        {
            throw new DataSourceException(
                $"读取失败,已重试{maxRetries}次: {ex.Message}", ex);
        }
    }
}

9.5 验证与异常

9.5.1 图层验证

public void ValidateLayer(OguLayer layer)
{
    var errors = new List<string>();
    
    // 基本验证
    if (string.IsNullOrWhiteSpace(layer.Name))
        errors.Add("图层名称不能为空");
    
    if (layer.Fields == null || layer.Fields.Count == 0)
        errors.Add("图层必须至少有一个字段");
    
    // 字段验证
    var fieldNames = new HashSet<string>();
    foreach (var field in layer.Fields)
    {
        if (string.IsNullOrWhiteSpace(field.Name))
            errors.Add("字段名称不能为空");
        
        if (!fieldNames.Add(field.Name))
            errors.Add($"字段名'{field.Name}'重复");
    }
    
    // 要素验证
    foreach (var feature in layer.Features)
    {
        if (string.IsNullOrWhiteSpace(feature.Wkt))
            errors.Add($"要素{feature.Fid}的几何为空");
        
        foreach (var attrName in feature.Attributes.Keys)
        {
            if (!fieldNames.Contains(attrName))
                errors.Add($"要素{feature.Fid}包含未定义的属性'{attrName}'");
        }
    }
    
    if (errors.Count > 0)
    {
        throw new LayerValidationException(
            $"图层验证失败:\n{string.Join("\n", errors)}")
        {
            Context = { ["ErrorCount"] = errors.Count }
        };
    }
}

9.5.2 几何验证

public void ValidateGeometry(string wkt, bool throwOnInvalid = true)
{
    try
    {
        var geom = GeometryUtil.Wkt2Geometry(wkt);
        
        // 空几何检查
        if (GeometryUtil.IsEmpty(geom))
        {
            if (throwOnInvalid)
                throw new TopologyException("几何为空");
            return;
        }
        
        // 有效性检查
        var validResult = GeometryUtil.IsValid(geom);
        if (!validResult.IsValid)
        {
            if (throwOnInvalid)
                throw new TopologyException($"几何无效: {validResult.ErrorMessage}");
        }
        
        // 简单性检查
        var simpleResult = GeometryUtil.IsSimple(geom);
        if (!simpleResult.IsSimple)
        {
            Console.WriteLine($"警告: 几何不简单 - {simpleResult.Reason}");
        }
    }
    catch (System.Exception ex) when (!(ex is TopologyException))
    {
        throw new FormatParseException($"WKT解析失败: {wkt}", ex);
    }
}

9.6 日志记录

9.6.1 结合日志框架

using Microsoft.Extensions.Logging;

public class LayerService
{
    private readonly ILogger<LayerService> _logger;
    
    public LayerService(ILogger<LayerService> logger)
    {
        _logger = logger;
    }
    
    public OguLayer ReadLayer(string path)
    {
        _logger.LogInformation("开始读取图层: {Path}", path);
        
        try
        {
            var layer = OguLayerUtil.ReadLayer(DataFormatType.SHP, path);
            _logger.LogInformation("读取成功,要素数: {Count}", layer.GetFeatureCount());
            return layer;
        }
        catch (DataSourceException ex)
        {
            _logger.LogError(ex, "数据源错误: {Message}", ex.Message);
            throw;
        }
        catch (OguException ex)
        {
            _logger.LogError(ex, "GIS操作错误: {Message}, 上下文: {@Context}", 
                ex.Message, ex.Context);
            throw;
        }
    }
}

9.6.2 异常监控

public static class ExceptionMonitor
{
    private static int _exceptionCount = 0;
    private static readonly ConcurrentDictionary<string, int> _exceptionTypes = new();
    
    public static void Track(OguException ex)
    {
        Interlocked.Increment(ref _exceptionCount);
        
        var typeName = ex.GetType().Name;
        _exceptionTypes.AddOrUpdate(typeName, 1, (_, count) => count + 1);
        
        // 可以发送到监控系统
        // SendToMonitoring(ex);
    }
    
    public static void PrintStats()
    {
        Console.WriteLine($"总异常数: {_exceptionCount}");
        foreach (var (type, count) in _exceptionTypes)
        {
            Console.WriteLine($"  {type}: {count}");
        }
    }
}

// 使用
try
{
    ProcessData();
}
catch (OguException ex)
{
    ExceptionMonitor.Track(ex);
    throw;
}

9.7 最佳实践

9.7.1 异常使用原则

// ✓ 使用具体的异常类型
throw new DataSourceException("文件不存在: data.shp");

// ✗ 避免使用通用异常
throw new Exception("文件不存在");

// ✓ 包含有用的上下文信息
throw new OguException("操作失败")
{
    Context = { ["Path"] = path, ["Operation"] = "Read" }
};

// ✓ 保留原始异常
catch (Exception ex)
{
    throw new DataSourceException("读取失败", ex);
}

// ✗ 吞掉异常
catch (Exception) { }  // 不要这样做

9.7.2 异常文档

/// <summary>
/// 读取图层
/// </summary>
/// <param name="path">文件路径</param>
/// <returns>图层对象</returns>
/// <exception cref="ArgumentException">路径为空时抛出</exception>
/// <exception cref="DataSourceException">无法打开数据源时抛出</exception>
/// <exception cref="FormatParseException">数据格式错误时抛出</exception>
public OguLayer ReadLayer(string path)
{
    if (string.IsNullOrWhiteSpace(path))
        throw new ArgumentException("路径不能为空", nameof(path));
    
    // ...
}

9.8 小结

本章介绍了OGU4Net的异常处理体系:

  1. 异常层次:以OguException为基类的完整异常体系
  2. 具体异常:DataSource、FormatParse、EngineNotSupported、LayerValidation、Topology
  3. 处理模式:基本处理、分层处理、过滤器、重试
  4. 验证机制:图层验证、几何验证
  5. 日志记录:结合日志框架,异常监控
  6. 最佳实践:使用具体异常、包含上下文、保留原始异常

良好的异常处理可以提高程序的健壮性,便于问题排查和调试。

posted @ 2025-12-03 15:51  我才是银古  阅读(0)  评论(0)    收藏  举报