ChuanGoing 2019-11-17
这篇原本时想把事务处理, 日志处理, 错误处理, 授权于鉴权一并介绍完的, 授权和鉴权我想结合自定义权限来介绍, 全部放到这里篇幅可能太长, 因此权限部分将会在下篇来介绍. 先说下我接下来的打算把, 下篇将介绍权限控制, 结合 OAuth2.0 和 OpenId Connect 以及自定义权限来介绍; 完了后会结合之前所介绍的基础来实现一个简单的电商网站, 当然时利用领域驱动设计来实现. 我的这个系列的主题就是领域驱动设计, 实现简单电商网站时将会深入的讲解下领域的划分原则及领域服务的场景, 中间可能会尝试部分业务实现事件驱动.
本篇学习曲线:
1. 日志记录
2. 错误处理
3. 事务处理
日志记录
NLog 是一个记录日志组件, 和 log4net 一样被广泛使用, 它可以将日志保存到文本文件, CSV, 控制台, VS 调试窗口, 数据库等. 在之前例子中的 webApi 项目中添加 NLog.Web.AspNetCore 的 Nuget 包, 并添加如下配置:
简单介绍下配置信息,"targets" 配置每个输出配置, 我这里有 3 个输出: database,allfile,ownfile, 分别表示输出到数据库和对应路径的日志文件下.
"rules" 规则配置了 4 条:
1. 将 Debug 以上级别 (含) 信息输出到 allfile
2. 忽略 Microsoft.* 开头的信息(对应的输出没有配置到任何文件), 此配置一般忽略即可
3. 将 Debug 以上级别 (含) 信息输出到 ownfile(注意这里配置和 allfile 一样, 一般配置级别高点的日志信息)
4. 将 Warn 以上级别 (含) 信息输出到数据库
完了后, 在 Program.cs Main 方法里面加入:
- var logger = NLogBuilder.ConfigureNLog($"Nlog.config").GetCurrentClassLogger();
- try
- {
- CreateWebHostBuilder(args).Build().Run();
- }
- catch (Exception ex)
- {
- logger.Error(ex, "Stopped program because of exception");
- throw ex;
- }
- finally
- {
- NLog.LogManager.Shutdown();
- }
注意不要忘了启用 NLog 组件使之生效
在 OrderController 的 Add 方法中加入以下代码:
用 postman 简单测试下, 我们可以看到执行目录中多出来了日志信息
错误处理
这里一般我们关心的错误大概有两类:
1. 内部错误, 即通过框架 (Mvc) 管道准确的传入到内部系统中并发生错误的此类信息
2. 框架 (Mvc) 执行管道的某些中间件时发生的错误或被中间件禁止继续访问的请求
因此, 定义如下 3 个类:
- public class InnerException : Exception
- {
- /// <summary>
- /// 内部错误代码
- /// </summary>
- public int? ErrorCode { get; }
- public InnerException(int errorCode) : base()
- {
- ErrorCode = errorCode;
- }
- public InnerException(int errorCode, string message) : base(message)
- {
- ErrorCode = errorCode;
- }
- public InnerException(int code, string message, Exception exception) : base(message, exception)
- {
- ErrorCode = code;
- }
- }
- InnerException
- public class MessageCodes
- {
- #region 公用
- /// <summary>
- /// 成功
- /// </summary>
- public const int Success = 20101000;
- /// <summary>
- /// 警告
- /// </summary>
- public const int Warning = 20102000;
- /// <summary>
- /// 错误
- /// </summary>
- public const int Error = 20103000;
- /// <summary>
- /// 数据验证错误
- /// </summary>
- public const int DataValidationError = 20104000;
- /// <summary>
- /// 数据不存在
- /// </summary>
- public const int DataNotFound = 20105000;
- /// <summary>
- /// 非法的数据状态
- /// </summary>
- public const int IllegalState = 20106000;
- /// <summary>
- /// 参数无效
- /// </summary>
- public const int InvalidParams = 20107000;
- /// <summary>
- /// 输入非法
- /// </summary>
- public const int IllegalInput = 20108000;
- /// <summary>
- /// 鉴权成功
- /// </summary>
- public const int AuthSuccess = 20109000;
- #endregion
- }
- MessageCodes
- public class WebException: InnerException
- {
- public HttpStatusCode HttpStatus { get; set; }
- public HttpRequest Request { get; private set; }
- public WebException(HttpStatusCode httpStatus, int errorCode, string message)
- : base(errorCode, message)
- {
- HttpStatus = httpStatus;
- }
- public WebException(HttpStatusCode httpStatus, int errorCode, string message, HttpRequest request)
- : this(httpStatus, errorCode, message)
- {
- Request = request;
- }
- public WebException(int errorCode, string message)
- : base(errorCode, message)
- {
- HttpStatus = HttpStatusCode.BadRequest;
- }
- }
- WebException
通过 Aop, 很方便就可以实现错误信息的处理:
- public class ExceptionFilter : IExceptionFilter
- {
- private readonly ILogger<ExceptionFilter> _logger;
- public ExceptionFilter(ILogger<ExceptionFilter> logger)
- {
- _logger = logger;
- }
- public void OnException(ExceptionContext context)
- {
- _logger.LogError(context.Exception, context.Exception.Message);
- #region IoC/automapper 等中间件对错误信息进行了包装, 需要解包
- //Web 错误: 验证 / 鉴权等
- var webException = GetException<Base.Exceptions.WebException>(context.Exception);
- if (webException != null)
- {
- context.Result = new JsonResult(new
- {
- ErrorCode = webException.ErrorCode ?? MessageCodes.Error,
- webException.Message
- })
- {
- StatusCode = (int)webException.HttpStatus
- };
- return;
- }
- // 内部错误
- var exception = GetException<InnerException>(context.Exception);
- if (exception != null)
- {
- context.Result = new JsonResult(new
- {
- ErrorCode = exception.ErrorCode ?? MessageCodes.Error,
- exception.Message
- })
- {
- StatusCode = (int)HttpStatusCode.InternalServerError
- };
- return;
- }
- #endregion
- }
- private TException GetException<TException>(Exception exception)
- where TException : Exception
- {
- if (exception == null)
- {
- return null;
- }
- if (exception is TException tException)
- {
- return tException;
- }
- else
- {
- return GetException<TException>(exception.InnerException);
- }
- }
- }
- ExceptionFilter
同时, Startup.cs 的 ConfigureServices 中注册一下:
- services.AddMvc(mvcOptions =>
- {
- mvcOptions.Filters.Add<ExceptionFilter>();
- })
即完成了错误信息并且错误信息会写入相应配置的输出中.
事务处理
UnitOfWork 又称工作单元, 为了保证数据操作完整性, 我们将处理数据的的操作统一放在一个事务中, 我们这里利用 UnitOfWork 来实现事务处理.
首先定义 IUnitOfWork 及 UnitOfWork 实现:
- public interface IUnitOfWork
- {
- void Begin(IsolationLevel level = IsolationLevel.Unspecified);
- void SaveChanges();
- void Failed();
- }
- View Code
- public class UnitOfWork : IUnitOfWork
- {
- private ITransactionRepository _repository;
- public UnitOfWork(ITransactionRepository repository)
- {
- _repository = repository;
- }
- public virtual void Begin(IsolationLevel level = IsolationLevel.Unspecified)
- {
- _repository.BeginTransaction(level);
- }
- public virtual void SaveChanges()
- {
- _repository.Commit();
- }
- public virtual void Failed()
- {
- _repository.Rollback();
- }
- }
- View Code
其中, UnitOfWork 依赖于:
- public interface ITransactionRepository
- {
- /// <summary>
- /// 打开事务
- /// </summary>
- /// <param name="level"></param>
- void BeginTransaction(IsolationLevel level = IsolationLevel.Unspecified);
- /// <summary>
- /// 提交事务
- /// </summary>
- void Commit();
- /// <summary>
- /// 事务回滚
- /// </summary>
- void Rollback();
- }
- ITransactionRepository
利用 DapperRepository 继承 ITransactionRepository 并实现:
- public virtual void BeginTransaction(IsolationLevel level = IsolationLevel.Unspecified)
- {
- DbContext.BeginTransaction(level);
- }
- public virtual void Commit()
- {
- DbContext.Commit();
- }
- public virtual void Rollback()
- {
- DbContext.RollBack();
- }
- View Code
基本功能实现, 如何使用呢? 这里还是需要利用 Aop:
- public class UnitOfWorkAttribute : AbstractInterceptorAttribute
- {
- public override Task Invoke(AspectContext context, AspectDelegate next)
- {
- if (context.Implementation is IApplicationService applicationService)
- {
- var uow = applicationService.UnitOfWork;
- uow.Begin();
- var aspectDelegate = next(context);
- if (aspectDelegate.Exception != null)
- {
- uow.Failed();
- throw aspectDelegate.Exception;
- }
- else
- {
- uow.SaveChanges();
- return aspectDelegate;
- }
- }
- else
- {
- return next(context);
- }
- }
- }
- UnitOfWorkAttribute
因此, 我们还需要在 Application 项目中添加如下代码:
- public class ServiceBase<TEntity, TPrimaryKey> : IApplicationService
- where TEntity : class, IEntity<TPrimaryKey>
- {
- protected IMapper Mapper { get; private set; }
- public virtual IUnitOfWork UnitOfWork { get; private set; }
- public ServiceBase(IComponentContext container, ICommandRepository<TEntity, TPrimaryKey> repository)
- {
- Mapper = container.Resolve<IMapper>();
- UnitOfWork = container.Resolve<IUnitOfWork>(new TypedParameter(typeof(ITransactionRepository), repository));
- }
- }
- ServiceBase
Application 中的每个服务去继承上面的 ServiceBase, 因此每个 Application 服务都具有了事务处理能力
详细代码请参考 GitHub 的 https://github.com/ChuanGoing/Start.git https://github.com/ChuanGoing/Start 的 Domain 分支可以找到.
来源: https://www.cnblogs.com/ChuanGoing/p/11879153.html