You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Web API如何按需返回正常响应或异常错误信息?

针对你遇到的Web API响应标准化问题,结合你想尽量少改动现有代码的需求,我整理了几个实用的解决方案,咱们一步步来看:

方案一:基于基类统一错误信息字段(优化你提到的思路)

这个方案的核心是给所有业务返回类加一个通用基类,不用逐个修改每个返回类的属性:

首先定义包含错误信息的基类:

public class BaseResponse
{
    public bool IsSuccess { get; set; } = true;
    public string ErrorMessage { get; set; }
    public string ErrorCode { get; set; } // 可选,用来区分不同错误类型,方便前端精准处理
}

然后让所有业务返回类继承这个基类,比如InfoResponseInfoRequest

public class InfoResponse : BaseResponse
{
    // 保留原有的业务属性
    public string InfoContent { get; set; }
    public int InfoId { get; set; }
}

public class InfoRequest : BaseResponse
{
    // 原有的请求相关属性(如果这个类作为返回值使用,调整对应字段即可)
}

接下来修改中间层方法,不再返回null,而是返回带错误信息的实例:

public InfoResponse ProcessInfoResponse(InfoModel info)
{
    try
    {
        var result = serviceLayer.Post<InfoModel>(info);
        if (result != null)
        {
            // 处理业务逻辑,组装正常返回数据
            return new InfoResponse 
            { 
                InfoContent = "处理后的有效内容",
                InfoId = 1001
            };
        }
        else
        {
            Log.Error("第三方API返回空结果");
            return new InfoResponse 
            { 
                IsSuccess = false,
                ErrorMessage = "获取信息失败:第三方API未返回有效数据",
                ErrorCode = "THIRD_PARTY_EMPTY"
            };
        }
    }
    catch (Exception ex)
    {
        Log.Error("处理信息时发生异常", ex);
        return new InfoResponse 
        { 
            IsSuccess = false,
            ErrorMessage = $"获取信息失败:{ex.Message}",
            ErrorCode = "PROCESS_ERROR"
        };
    }
}

最后调整API控制器逻辑,根据IsSuccess字段返回对应状态码:

[HttpPost]
public IActionResult GetInfo([FromBody] InfoModel info)
{
    try
    {
        var result = new Info().ProcessInfoResponse(info);
        if (!result.IsSuccess)
        {
            // 根据错误码区分返回状态码,比如第三方错误返回400,内部错误返回500
            return result.ErrorCode.StartsWith("THIRD_PARTY") ? BadRequest(result) : StatusCode(500, result);
        }
        return Ok(result);
    }
    catch (Exception e)
    {
        Log.Error("API层未捕获异常", e);
        return StatusCode(500, new BaseResponse 
        { 
            IsSuccess = false,
            ErrorMessage = "服务器内部错误",
            ErrorCode = "API_UNHANDLED"
        });
    }
}

这个方案改动量极小,只需要给返回类加继承、修改中间层的返回逻辑,完全兼容现有代码结构。

方案二:使用泛型响应包装类(侵入性更低)

如果你不想修改现有业务返回类的结构,可以用泛型包装类把业务数据和错误信息打包:

先定义泛型包装类:

public class ApiResponse<T>
{
    public bool IsSuccess { get; set; } = true;
    public T Data { get; set; }
    public string ErrorMessage { get; set; }
    public string ErrorCode { get; set; }
}

然后修改中间层方法的返回类型为这个泛型类:

public ApiResponse<InfoResponse> ProcessInfoResponse(InfoModel info)
{
    try
    {
        var result = serviceLayer.Post<InfoModel>(info);
        if (result != null)
        {
            // 组装业务数据
            var data = new InfoResponse 
            { 
                InfoContent = "处理后的有效内容",
                InfoId = 1001
            };
            return new ApiResponse<InfoResponse> { Data = data };
        }
        else
        {
            Log.Error("第三方API返回空结果");
            return new ApiResponse<InfoResponse> 
            { 
                IsSuccess = false,
                ErrorMessage = "获取信息失败:第三方API未返回有效数据",
                ErrorCode = "THIRD_PARTY_EMPTY"
            };
        }
    }
    catch (Exception ex)
    {
        Log.Error("处理信息时发生异常", ex);
        return new ApiResponse<InfoResponse> 
        { 
            IsSuccess = false,
            ErrorMessage = $"获取信息失败:{ex.Message}",
            ErrorCode = "PROCESS_ERROR"
        };
    }
}

控制器层代码调整如下:

[HttpPost]
public IActionResult GetInfo([FromBody] InfoModel info)
{
    try
    {
        var result = new Info().ProcessInfoResponse(info);
        if (!result.IsSuccess)
        {
            return result.ErrorCode.StartsWith("THIRD_PARTY") ? BadRequest(result) : StatusCode(500, result);
        }
        return Ok(result);
    }
    catch (Exception e)
    {
        Log.Error("API层未捕获异常", e);
        return StatusCode(500, new ApiResponse<object> 
        { 
            IsSuccess = false,
            ErrorMessage = "服务器内部错误",
            ErrorCode = "API_UNHANDLED"
        });
    }
}

这个方案完全不用修改原有业务模型,只需要新增一个泛型类,适合不想动历史代码的场景。

方案三:自定义异常 + 全局异常过滤器(代码更简洁)

如果想让错误处理逻辑更清晰、职责更分离,可以用自定义异常配合全局过滤器,让中间层只负责业务逻辑,错误处理交给全局过滤器:

首先定义自定义业务异常:

public class BusinessException : Exception
{
    public string ErrorCode { get; }
    public int StatusCode { get; }

    public BusinessException(string message, string errorCode, int statusCode = 400) : base(message)
    {
        ErrorCode = errorCode;
        StatusCode = statusCode;
    }
}

然后修改中间层方法,遇到错误时直接抛出异常,不用返回null

public InfoResponse ProcessInfoResponse(InfoModel info)
{
    var result = serviceLayer.Post<InfoModel>(info);
    if (result != null)
    {
        // 处理业务逻辑,返回正常数据
        return new InfoResponse 
        { 
            InfoContent = "处理后的有效内容",
            InfoId = 1001
        };
    }
    else
    {
        Log.Error("第三方API返回空结果");
        throw new BusinessException("获取信息失败:第三方API未返回有效数据", "THIRD_PARTY_EMPTY", 400);
    }
}

接下来创建全局异常过滤器,统一处理所有异常:

public class GlobalExceptionFilter : IExceptionFilter
{
    private readonly ILogger<GlobalExceptionFilter> _logger;

    public GlobalExceptionFilter(ILogger<GlobalExceptionFilter> logger)
    {
        _logger = logger;
    }

    public void OnException(ExceptionContext context)
    {
        var response = new BaseResponse
        {
            IsSuccess = false
        };

        if (context.Exception is BusinessException businessEx)
        {
            response.ErrorMessage = businessEx.Message;
            response.ErrorCode = businessEx.ErrorCode;
            context.HttpContext.Response.StatusCode = businessEx.StatusCode;
        }
        else
        {
            response.ErrorMessage = "服务器内部错误";
            response.ErrorCode = "UNHANDLED_ERROR";
            context.HttpContext.Response.StatusCode = 500;
            _logger.LogError(context.Exception, "未捕获的服务器异常");
        }

        context.Result = new JsonResult(response);
        context.ExceptionHandled = true;
    }
}

Program.cs(或Startup.cs)中注册这个过滤器:

// .NET 6+ 写法
builder.Services.AddControllers(options =>
{
    options.Filters.Add<GlobalExceptionFilter>();
});

最后控制器的代码可以大幅简化:

[HttpPost]
public IActionResult GetInfo([FromBody] InfoModel info)
{
    var result = new Info().ProcessInfoResponse(info);
    return Ok(result);
}

这个方案让代码职责更清晰:中间层专注业务逻辑,错误处理交给全局过滤器,后期维护起来更方便。

方案选择建议

  • 如果想尽量少改动现有代码,方案一是最优选择;
  • 如果不想修改原有业务模型,方案二更适合;
  • 如果希望代码结构更清晰、职责更分离,方案三是长期维护的更好选择。

内容的提问来源于stack exchange,提问作者XamDev

火山引擎 最新活动