本系列目录:Abp介绍和经验分享-目录

前言

ABP中有个异常UserFriendlyException经常被使用,但是它所在的命名空间是Abp.UI,总觉得和展现层联系过于紧密,在AppService中用也就罢了,在领域层中用它总觉得有点不合适。
那么怎么定义业务异常?既要用起来舒服又能体现业务意义?

几点目标

  1. 无需每个业务领域都定义各自的异常类,但使用时要有一定的可读性,能区分不同业务;
  2. 要有错误码;
  3. 每个错误码对应的提示语不能硬编码,最好能使用已有的本地化语言机制;
  4. 要有日志级别,不同的业务异常应该可以设置不同的日志级别,无特殊要求,应默认Warn级别;

以下代码从本系列QuickStartA中的Personball.Demo解决方案开始

先从错误码入手

从上面的要求来看,错误码定义成枚举最合适不过:

  1. 每个枚举值,既可以表达成字符串,又可以表达成数字;
  2. 枚举值字符串,在使用时可读性好,也可以直接作为本地化语言键值对中的键名;
  3. 枚举值数字,直接对应AjaxResponse中Error部分的Code(int类型);

我们打开Personball.Demo的解决方案,在Personball.Demo.Core项目的根目录中添加枚举定义ErrorCode

public enum ErrorCode : int
{//用region提前分配错误编码给各个业务领域//防止团队成员开发时发生冲突#region 库存相关 100-110/// <summary>/// 库存不足/// </summary>InventoryNotEnough = 100,#endregion#region 购物车相关 200-299/// <summary>/// 购物车项不存在/// </summary>ShoppingCartItemNotExists = 200,/// <summary>/// 商品已下架/// </summary>ShoppingCartItemIsShelve = 201,/// <summary>/// 数量超出范围/// </summary>ShoppingCartItemQtyMoreThanRange = 202,#endregion#region 订单中心 600-649/// <summary>/// 订单项重复/// </summary>OrderItemDuplicate = 601,/// <summary>/// 订单优惠(折扣)项重复/// </summary>OrderDiscountDuplicate = 602,#endregion
}

针对错误码的定义,强烈建议使用英文单词描述该错误发生的原因,而不是仅仅表达一个结果或者现象。
比如,【订单项重复】和【订单优惠项重复】比一个笼统的【订单创建失败】要好。
英文单词可以尽可能长,如果觉得看不清楚,也可以采用下划线分隔。
如果英文能力不足,就尽量在注释上描述清楚,注释在最后可以统一录入语言文件作为本地化提示语。

给几个例子:

/// <summary>
/// 团队申请人或挑选的成员已创建过团队,不能重复创建
/// </summary>
PspOrganizationLeaderOrMembersAlreadyHasOrgs = 412,/// <summary>
/// 非消费者等级的账号只能属于自己的团队!
/// </summary>
PspAccountCannotChangeOrganizationWhenLevelGreaterThanLevelOne = 414,/// <summary>
/// 创建新团队时,只能包含Leader直接推荐的同级成员
/// </summary>
PspOrganizationCreateNewCanOnlyIncludeLeaderChildrenWhichInSameLevel = 417,

定义业务异常

现在我们已经有了ErrorCode,而且ErrorCode很好的表达了业务到底发生了什么,接着我们看看异常怎么定义。
我们一开始定的几个目标,其实大部分已经被枚举化的ErrorCode结合本地化语言机制满足了,所以我们的业务异常肯定包含这个ErrorCode枚举:

public class DemoBusinessException : AbpException, IHasErrorCode, IHasLogSeverity
{public DemoBusinessException(ErrorCode errorCode): base(errorCode.ToString()){Code = (int)errorCode;}public DemoBusinessException(ErrorCode errorCode, string message): base(message){Code = (int)errorCode;}public DemoBusinessException(ErrorCode errorCode, Exception innerException): base(errorCode.ToString(), innerException){Code = (int)errorCode;}public DemoBusinessException(ErrorCode errorCode, string message, Exception innerException): base(message, innerException){Code = (int)errorCode;}public int Code { get; set; }public LogSeverity Severity { get; set; } = LogSeverity.Warn;
}
  1. 继承AbpException,其中base(errorCode.ToString())构造方法中的参数对应的是AbpExceptionstring message,我们用枚举值ToString是为了之后方便处理提示语本地化。当然从重载提供的多个构造方法来看,也是支持覆盖这套机制的,可以直接写message。
  2. 继承IHasErrorCode,是为了告诉异常Handle代码,这个异常携带了错误码。
  3. 继承IHasLogSeverity,是为了告诉异常Handle代码,这个异常应该以哪个日志级别进行记录。

这个业务异常的使用范例:

//典型用法
throw new DemoBusinessException(ErrorCode.InventoryNotEnough);
//直接硬编码提示语
throw new DemoBusinessException(ErrorCode.InventoryNotEnough, $"{item.Name}库存不足!");
//设置日志等级
throw new DemoBusinessException(ErrorCode.InventoryNotEnough)
{Severity = Abp.Logging.LogSeverity.Error
};

最后,异常Handle代码

刚才提到的异常Handle代码,其实Abp提供了很好的扩展,就是IExceptionToErrorInfoConverter,先看看如何注册自定义实现:
Personball.Demo.Web项目,App_Start目录下的DemoWebModule

public override void PostInitialize()
{var errorInfoBuilder = IocManager.Resolve<IErrorInfoBuilder>();errorInfoBuilder.AddExceptionConverter(IocManager.Resolve<CustomExceptionErrorInfoConverter>());
}

其中CustomExceptionErrorInfoConverter,就是我们要自定义的类:

public class CustomExceptionErrorInfoConverter: IExceptionToErrorInfoConverter, ITransientDependency
{private readonly ILocalizationManager _localizationManager;public IExceptionToErrorInfoConverter Next { set; private get; }public CustomExceptionErrorInfoConverter(ILocalizationManager localizationManager){_localizationManager = localizationManager;}public ErrorInfo Convert(Exception exception){while (exception is AggregateException && exception.InnerException != null){exception = exception.InnerException;}if (exception is DemoBusinessException){var ex = exception as DemoBusinessException;return new ErrorInfo(ex.Code, L(ex.Message));}if (exception is EntityNotFoundException){return new ErrorInfo((int)ErrorCode.ItemNotExists, L(ErrorCode.ItemNotExists.ToString()));}if (exception is ArgumentException){var argEx = exception as ArgumentException;var argMsg = exception.Message ?? L(ErrorCode.RequestParametersError.ToString());return new ErrorInfo((int)ErrorCode.RequestParametersError, argMsg, $"ParamName:{argEx.ParamName}");}return Next.Convert(exception);}private string L(string name){try{return _localizationManager.GetString(DemoConsts.LocalizationSourceName, name);}catch (Exception){return name;}}
}

可以启动看看效果了

我们在登录处理逻辑上抛个业务异常看看效果。
找到Personball.Demo.Web下的AccountController,添加一行如下:

[HttpPost]
[DisableAuditing]
public async Task<JsonResult> Login(LoginViewModel loginModel, string returnUrl = "", string returnUrlHash = "")
{//在此抛出业务异常throw new DemoBusinessException(ErrorCode.InventoryNotEnough);CheckModelState();//...略
}

运行后,点击登陆,如图:

看响应中的错误码,是100,提示语是[Inventory not enough],因为忘了配置语言文件,所以这里没本地化。

打开Personball.Demo.Core下的目录Localization\Source,编辑Demo-zh-CN.xml,追加一行:

<text name="InventoryNotEnough" value="库存不足"/>

再运行之前的登陆看看,是不是变成中文了?

本文源码下载

Personball.Demo.ErrorCode.7z

转载于:https://www.cnblogs.com/personball/p/7455951.html

[2017-08-28]Abp系列——业务异常与错误码设计及提示语的本地化相关推荐

  1. C++异常 返回错误码

    C++异常 返回错误码 参考文章: (1)C++异常 返回错误码 (2)https://www.cnblogs.com/moonlightpoet/p/5670343.html 备忘一下.

  2. java 错误码设计_关于Java中异常的设计

    Java中异常分为checked 和 unchecked 两种. 首先, 什么时候使用checked,什么时候用unchecked? checked本意是要求调用方处理这个异常, unchecked ...

  3. fetion飞信登录异常,错误码10033201、10033202

    新安装的飞信2012,登陆异常. 1.  第一次登陆失败,提示"错误码10033201", 解决方法: 登陆界面 --> 网络设置 --> 更多设置 --> 登陆 ...

  4. Modbus功能码/异常功能码/错误码

    Modbus协议主要构成是地址码/标识码,功能码,寄存器地址,数据报文等内容.由于modbus协议是请求/应答通信协议,其其中功能码主要用于表述该数据报文执行的功能,当服务器对客户机进行响应时,它使用 ...

  5. Replace Error Code with Exception(以异常取代错误码)

    某个函数返回一个特定的代码,用来表示某种错误情况 public int withdraw(int amount) {if (amount > balance) {return -1;} else ...

  6. java捕获sybase主键重复异常_sybase错误码

    入事务日志文件时出错 3621 –299 语句被用户中断 3702 –214 表正在使用 3702 –215 '%1' 正在使用过程 3702 –750 用户拥有正在使用的过程 3702 –751 用 ...

  7. 服务器ec系列,Cloud_EC服务端错误码大全

    在服务端开发过程中,会经常返回错误码,如果没有对应的错误码描述,仅仅有一个返回码很 难定位问题,这样进展和效率都会很低下! 这里将常见的错误码与对应的描述总结在这里,后续会一直进行追加与完善,详情如下 ...

  8. 爱奇艺智能前端异常监控平台的设计与实践

    背景 前端监控一般包括三方面:异常监控.性能监控(First Meaningful Paint.First Contentful Paint等性能指标监控)及行为数据监控(PV.UV.页面停留时长等监 ...

  9. 关于MySQL中date字段默认值为“0000-00-00 00:00:00“导致MyBatisPlus无法正常list()而报系统异常错误码500的问题

    今天在SpringBoot整合MyBatisPlus的时候,使用自动代码生成的功能,对User表生成了entity.mapper.service代码,之后编写测试类调用userService.list ...

  10. 一周技术学习笔记(第65期)-到底是返回错误码还是返回异常信息

    1.分布式程序调用到底是返回错误码还是返回错误信息 分布式系统环境里面,RPC往往被我们称为:连接各个服务之间的纽带.当然,也有MQ.如果纽带这个词显得高雅,通俗点就是信息交互,或者通信. 好,还是说 ...

最新文章

  1. python3 判断进程是否存在
  2. python是个啥-Python是个什么鬼?师兄用它一年发表5篇SCI!
  3. 牛客网多校第4场 D Another Distinct Values 【构造】
  4. Challenge: Machine Learning Basics
  5. 【struts2+spring+hibernate】ssh框架整合开发
  6. 高德地图天气图标符号大全_共享雨伞,高德这波营销格外暖!
  7. 等了这么久,就给我看这个?
  8. 我为什么要学习C++反汇编
  9. jdk注解_我们正在下注:这个注解很快就会出现在JDK中
  10. Linux:init0和shutdown -h哪个用来关机比较安全【转载】
  11. 不要再用main方法测试代码性能了,用这款JDK自带工具
  12. Linux工作笔记-RabbitMQ的安装
  13. MQTT连接服务器返回2
  14. pytorch nn.Linear
  15. bzoj4552[HEOI2016/TJOI2016]排序(二分+线段树)
  16. Win7 64位系统安装SQL Server2005
  17. Python: 用matplotlib.pyplot,绘制 cos 与 sin 函数图像
  18. 第2章 藏书阁签到,修为突破
  19. Photoshop CC 2019 软件安装教程
  20. 2020-09-13 滴滴-2021校招在线笔试-DE数据开发试卷

热门文章

  1. Windows 10 下,强制关闭端口
  2. warning: cast to pointer from integer of different size [-Wint-to-pointer-cast]错误
  3. Nacos——Distro一致性协议(架构篇)
  4. 论软件测试工程师面试套路和暗语灵魂解密
  5. jquery设置禁止浏览器刷新
  6. 适合程序员的英文名字
  7. Mysql和Oracle实现序列自增
  8. 高效能人士的七个习惯(一)由内而外全面造就自己
  9. Cadence 16.6快速创建多引脚芯片原理图符号
  10. 用二分法求下面方程在(-10,10)的根:2x3-4x2+3x-6=0