初始化一个Abpvnext项目


一、安装 ABP CLI

安装

dotnet tool install -g Volo.Abp.Studio.Cli

更新

dotnet tool update -g Volo.Abp.Studio.Cli

二、使用CLI创建项目

abp new Demo.CMS -t module --no-ui -csf  -dbms MySQL

命令解析

Demo.CMS:公司名称.项目名称
-t:指定模板名称
no-ui:无用户界面
-csf:指定项目是位于输出文件夹中的新文件夹中,还是直接位于输出文件夹中。
-dbms:设置所使用的数据库

官网连接

ABP CLI

三、调整项目结构

  1. 创建CMS.Shared解决方案,用于存放一些共享的代码,如全局返回模型、帮助类等。
  2. 创建Demo.CMS.Swagger,用于存放Swagger相关代码。
  3. 创建Demo.CMS.Caching,用于存放缓存相关代码。
    在这里插入图片描述

四、配置统一返回结果

返回模型统一放在CMS.Shared

在这里插入图片描述

1.创建响应码枚举

namespace CMS.Shared.Base.Enum
{
    /// <summary>
    /// 响应码枚举
    /// </summary>
    public enum ResponseResultCode
    {
        /// <summary>
        /// 成功
        /// </summary>
        Succeed = 0,

        /// <summary>
        /// 失败
        /// </summary>
        Failed = 1,
    }
}

2.创建响应实体

using CMS.Shared.Base.Enum;

namespace CMS.Shared.Base
{
    /// <summary>
    /// 响应实体
    /// </summary>

    public class ResponseResult
    {
        /// <summary>
        /// 响应码
        /// </summary>
        public ResponseResultCode Code { get; set; }

        /// <summary>
        /// 响应信息
        /// </summary>
        public string Message { get; set; }

        /// <summary>
        /// 成功
        /// </summary>
        public bool Success => Code == ResponseResultCode.Succeed;

        /// <summary>
        /// 时间戳(毫秒)
        /// </summary>
        public long Timestamp { get; } = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();

        /// <summary>
        /// 响应成功
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        public void IsSuccess(string message = "")
        {
            Message = message;
            Code = ResponseResultCode.Succeed;
        }

        /// <summary>
        /// 响应失败
        /// </summary>
        /// <param name="message"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        public void IsFailed(string message = "")
        {
            Message = message;
            Code = ResponseResultCode.Failed;
        }

        /// <summary>
        /// 响应失败
        /// </summary>
        /// <param name="exception"></param>
        public void IsFailed(Exception exception)
        {
            Message = exception.InnerException?.StackTrace!;
            Code = ResponseResultCode.Failed;
        }
    }
}

3.创建响应实体(泛型)

using CMS.Shared.Base.Enum;

namespace CMS.Shared.Base
{
    /// <summary>
    /// 响应实体(泛型)
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class ResponseResult<T> : ResponseResult where T : class
    {
        /// <summary>
        /// 返回结果
        /// </summary>
        public T? Result { get; set; }

        /// <summary>
        /// 响应成功
        /// </summary>
        /// <param name="result"></param>
        /// <param name="message"></param>
        public void IsSuccess(T? result = null, string message = "")
        {
            Message = message;
            Code = ResponseResultCode.Succeed;
            Result = result;
        }
    }
}

五、配置并使用统一返回结果过滤器

在这里插入图片描述

1.创建NoWrapperAttribute

using System;

namespace Demo.CMS.Attribute
{
    /// <summary>  
    /// 无需包装返回结果特性  
    /// 该特性用于标记在控制器类或方法上,表示其返回结果不需要被ResultWrapperFilter过滤器进行统一包装。  
    /// </summary>  
    [AttributeUsage(
        AttributeTargets.Class | AttributeTargets.Method, // 指定该特性可应用于类和方法  
        AllowMultiple = false, // 指定一个元素上不能多次应用此特性  
        Inherited = false      // 指定该特性不会被子类继承  
    )]
    public class NoWrapperAttribute : System.Attribute
    {
        // 由于这是一个标记特性(Marker Attribute),通常不包含任何属性或方法  
        // 它仅仅用于在运行时通过反射等方式被检测到  
    }
}

2.创建ResultWrapperFilter

using CMS.Shared.Base;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc;
using System.Linq;
using CMS.Shared.Base.Enum;
using Demo.CMS.Attribute;

namespace Demo.CMS.Filter
{
    /// <summary>  
    /// 全局统一返回结果过滤器  
    /// 用于自动将控制器的返回结果包装成统一的响应格式。  
    /// 如果控制器或其方法上标记了NoWrapperAttribute,则不进行包装,直接返回原始值。  
    /// </summary>  
    public class ResultWrapperFilter : ActionFilterAttribute
    {
        /// <summary>  
        /// 在结果执行前调用,用于包装返回结果。  
        /// </summary>  
        /// <param name="context">当前结果执行的上下文信息。</param>  
        public override void OnResultExecuting(ResultExecutingContext context)
        {
            // 尝试获取当前控制器动作描述符  
            var controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;

            // 检查控制器方法上是否有NoWrapperAttribute特性  
            var actionWrapper = controllerActionDescriptor?.MethodInfo.GetCustomAttributes(typeof(NoWrapperAttribute), false).FirstOrDefault();

            // 检查控制器上是否有NoWrapperAttribute特性  
            var controllerWrapper = controllerActionDescriptor?.ControllerTypeInfo.GetCustomAttributes(typeof(NoWrapperAttribute), false).FirstOrDefault();

            // 如果包含NoWrapperAttribute,则说明不需要对返回结果进行包装,直接返回原始值  
            if (actionWrapper != null || controllerWrapper != null)
            {
                return;
            }

            // 创建一个新的响应结果对象  
            var rspResult = new ResponseResult<object>();

            // 检查当前结果是否为ObjectResult类型  
            if (context.Result is ObjectResult)
            {
                var objectResult = context.Result as ObjectResult;

                // 如果ObjectResult的值为null,则设置响应状态为失败,并返回错误信息  
                if (objectResult?.Value == null)
                {
                    rspResult.Code = ResponseResultCode.Failed;
                    rspResult.Message = "未找到资源";
                    context.Result = new ObjectResult(rspResult);
                }
                else
                {
                    // 如果返回结果已经是ResponseResult<T>类型的,则不需要进行再次包装  
                    if (objectResult.DeclaredType.IsGenericType && objectResult.DeclaredType?.GetGenericTypeDefinition() == typeof(ResponseResult<>))
                    {
                        return;
                    }

                    // 否则,将原始结果设置到rspResult的Result属性中,并更新context的Result为包装后的结果  
                    rspResult.Result = objectResult.Value;
                    context.Result = new ObjectResult(rspResult);
                }
            }
        }
    }
}

3.在HttpApiHostModule中使用

        context.Services.AddControllers(options =>
        {
            // 全局统一返回结果过滤器
            options.Filters.Add<ResultWrapperFilter>();
        });

六、配置全局异常捕获

在这里插入图片描述

1.创建GlobalExceptionMiddleware

using System;
using System.Net;
using System.Threading.Tasks;
using CMS.Shared.Base;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using Serilog;

namespace Demo.CMS.Middlewares
{
    /// <summary>
    /// 全局异常处理中间件
    /// </summary>
    /// <param name="next"></param>
    public class GlobalExceptionMiddleware
    {
        private readonly RequestDelegate _next;

        public GlobalExceptionMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            try
            {
                await _next(context);
            }
            catch (Exception ex)
            {
                Log.Error(ex, "!!!发生了一个异常!!!");

                context.Response.ContentType = "application/json";
                context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;

                var res = new ResponseResult();
                res.IsFailed(ex.Message + ",请查看日志!");
                var json = JsonConvert.SerializeObject(res);

                await context.Response.WriteAsync(json);
            }
        }
    }
}

2.移除Abp自带的过滤器

        Configure<MvcOptions>(options =>
        {
            var filterMetadata = options.Filters.FirstOrDefault(x => x is ServiceFilterAttribute attribute && attribute.ServiceType == typeof(AbpExceptionFilter));
            // 移除 AbpExceptionFilter,不然会和自定义全局异常捕获有冲突
            options.Filters.Remove(filterMetadata);
        });

3.在HttpApiHostModule中使用

 app.UseMiddleware<GlobalExceptionMiddleware>();

写在最后

任何问题评论区或私信讨论,欢迎指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值