zoukankan      html  css  js  c++  java
  • NET5 GRPC 全局异常处理,参数校验,swagger文档生成,以及 GRPC 提供 web api 接口

    实现得很傻逼....别问为什么...问就是新手瞎几把写

    先上效果图

     

    怎么创建 grpc server 我就不说了...

    先安装一下需要的玩意

    Calzolari.Grpc.Net.Client.Validation

    Microsoft.AspNetCore.Grpc.Swagger  (这是预览版的记得勾选上)

     /// <summary>
        /// 业务异常类
        /// </summary>
        public class BusinessException : Exception
        {
            public BusinessException(int code, string msg)
            {
                Code = code;
                Msg = msg;
            }
    
            /// <summary>
            /// 错误码
            /// </summary>
            public int Code { get; set; }
    
            /// <summary>
            /// 错误信息
            /// </summary>
            public string Msg { get; set; } = string.Empty;
    
            /// <summary>
            /// 服务器未知错误
            /// </summary>
            /// <returns></returns>
            public static BusinessException UnknownError() => new(-1, "未知错误");
      }

    GRPC 中的异常用拦截器来处理

    /// <summary>
        /// grpc 全局异常处理拦截器
        /// </summary>
        public class ExceptionInterceptor : Interceptor
        {
            private readonly ILogger<ExceptionInterceptor> logger;
    
            public ExceptionInterceptor(ILogger<ExceptionInterceptor> logger)
            {
                this.logger = logger;
            }
    
            public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(TRequest request, ServerCallContext context, UnaryServerMethod<TRequest, TResponse> continuation)
            {
                try
                {
                    return await continuation(request, context);
                }
                catch (Exception e)
                {
                    if (e is RpcException re)
                    {
                        if (re.StatusCode == StatusCode.InvalidArgument)
                        {
                            string base64ErrStr = re.Trailers.GetValue("validation-errors-text");
                            byte[] bytes = Convert.FromBase64String(base64ErrStr);
                            string jsonErrorStr = Encoding.UTF8.GetString(bytes);
                            List<ValidationFailure> validationFailures = JsonConvert.DeserializeObject<List<ValidationFailure>>(jsonErrorStr);
                            re.Trailers.Clear();
                            if (validationFailures.Count > 0)
                            {
                                string message = string.Empty;
                                validationFailures.ForEach(err =>
                                {
                                    message += $"{err.ErrorMessage},";
                                });
                                message = message[0..^1];
                                throw new RpcException(new Status(StatusCode.InvalidArgument, message));
                            }
                            throw;
                        }
                    }
                    if (e is BusinessException be)
                    {
                        throw new RpcException(new Status(StatusCode.FailedPrecondition, be.Msg));
                    }
                    logger.LogError(e.ToString());
                    throw new RpcException(new Status(StatusCode.Internal, "Server internal error, contact administrator!"));
                }
            }
        }

     因为要把 grpc 转换成 web api ,为了返回友好的格式要自己处理一下(实现得很傻逼...主要是不知道怎么把 RpcException 第二个参数 Metadata 元数据信息在 HttpContext 中读取出来...不然可以通过元数据传递错误信息,有知道的老哥可以回复一下,谢谢)

    用中间件来处理 web api 的请求

     /// <summary>
        /// web api 全局异常处理中间件
        /// </summary>
        public class ExceptionMiddleware
        {
            private readonly RequestDelegate next;
            private readonly ILogger<ExceptionInterceptor> logger;
    
            public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionInterceptor> logger)
            {
                this.next = next;
                this.logger = logger;
            }
    
            public async Task Invoke(HttpContext context)
            {
                try
                {
                    StringValues stringValues = context.Request.Headers["accept"];
                    if (stringValues.Equals("application/json"))
                    {
                        // TODO 实现的有问题,性能不会太好,目前不知道怎么从 HttpContext 读取 GRPC 的元数据,不然可以通过他获取错误消息
                        var responseOriginalBody = context.Response.Body;
                        using var ms = new MemoryStream();
                        context.Response.Body = ms;
                        await next.Invoke(context);
                        if (context.Response.StatusCode != 200)
                        {
                            ms.Seek(0, SeekOrigin.Begin);
                            using var responseReader = new StreamReader(ms);
                            var responseContent = await responseReader.ReadToEndAsync();
                            ms.Seek(0, SeekOrigin.Begin);
                            Dictionary<object, object> dictionary = JsonConvert.DeserializeObject<Dictionary<object, object>>(responseContent);
                            string error = (string)dictionary["error"];
                            string indexStr = "Detail="";
                            int index = error.LastIndexOf(indexStr);
                            string detail = error.Substring(index + indexStr.Length, error.LastIndexOf("")") - index - indexStr.Length);
                            ResponseData rd = new(null, false, detail);
                            string rdJson = JsonConvert.SerializeObject(rd);
                            byte[] rdJsonBytes = Encoding.UTF8.GetBytes(rdJson);
                            using var ms2 = new MemoryStream();
                            await ms2.WriteAsync(rdJsonBytes.AsMemory(0, rdJsonBytes.Length));
                            ms2.Seek(0, SeekOrigin.Begin);
                            await ms2.CopyToAsync(responseOriginalBody);
                        }
                        else
                        {
                            ms.Seek(0, SeekOrigin.Begin);
                            using var responseReader = new StreamReader(ms);
                            var responseContent = await responseReader.ReadToEndAsync();
                            ms.Seek(0, SeekOrigin.Begin);
                            ResponseData rd = new(JsonConvert.DeserializeObject(responseContent), true, "");
                            string rdJson = JsonConvert.SerializeObject(rd);
                            byte[] rdJsonBytes = Encoding.UTF8.GetBytes(rdJson);
                            using var ms2 = new MemoryStream();
                            await ms2.WriteAsync(rdJsonBytes.AsMemory(0, rdJsonBytes.Length));
                            ms2.Seek(0, SeekOrigin.Begin);
                            await ms2.CopyToAsync(responseOriginalBody);
                        }
                    }
                    else
                    {
                        await next.Invoke(context);
                    }
                }
                catch (Exception ex)
                {
                    logger.LogError(ex.ToString());
                    throw;
                }
            }
    
            public class ResponseData
            {
                public ResponseData(object data, bool success, string errorMsg)
                {
                    Data = data;
                    Success = success;
                    ErrorMsg = errorMsg;
                }
    
                [JsonProperty("data")]
                public object Data { get; set; }
    
                [JsonProperty("sucess")]
                public bool Success { get; set; }
    
                [JsonProperty("error_msg")]
                public string ErrorMsg { get; set; }
            }
        }

    接下来就是 grpc 怎么做参数校验

    随便来一个 proto 服务

    syntax = "proto3";
    import "google/api/annotations.proto";
    // 用户服务
    service UserGrpcService {
    
        // 用户登录
        rpc Login (UserLoginRequest) returns (UserLoginReply){
            option (google.api.http) = {
                post: "/v1/user/login",
                body: "*"
            };
        }
    
    }
    
    
    message UserLoginRequest {
        string user_name = 1; // 用户名
        string password = 2; // 密码
    }

    定义参数校验,  看到下面的 UserLoginRequest 这玩意了吗...这是 根据 proto 文件自动生成的类型 和 proto中的 参数一样的,这样定义了就可以校验了, 记得添加 services.AddValidator<Login>();

     public class Login : AbstractValidator<UserLoginRequest>
            {
                public Login()
                {
                    RuleFor(r => r.UserName).MaximumLength(12);
                    RuleFor(r => r.Password).MaximumLength(12);
                }
            }
     public void ConfigureServices(IServiceCollection services)
            {
               
    
                services.AddSingleton(HtmlEncoder.Create(UnicodeRanges.All));
    
                _ = services.AddGrpc(options =>
                {
                    options.Interceptors.Add<ExceptionInterceptor>();
                    options.EnableMessageValidation();
                });
                services.AddValidator<Login>();
                services.AddGrpcValidation();
                services.AddGrpcHttpApi();
                services.AddSwaggerGen(c =>
                {
                    c.SwaggerDoc("v1", new OpenApiInfo { Title = "My Api", Version = "v1" });
                   
                });
                services.AddGrpcSwagger();
            }


    public void Configure(IApplicationBuilder app)
            {
                app.UseMiddleware<ExceptionMiddleware>();
                app.UseSwagger();
                app.UseSwaggerUI(c =>
                {
                    c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
                });
                _ = app.UseRouting();
                _ = app.UseEndpoints(endpoints =>
                  {
                      添加自己的服务
                  });
            }

     proto文件怎么编写直接看微软的吧 从 gRPC 创建 JSON Web API | Microsoft Docs

    写得很乱...当个笔记了,实现的效果就是 业务中抛出  BusinessException  异常,或者参数校验抛出的异常,grpc拦截器中捕获然后修改一下格式再次抛出友好的异常给客户端,然后 http 的中间件中判断一下请求来自grpc 客户端还是通过 http api 接口访问过来的,如果是http api 就封装统一格式.

    
    
  • 相关阅读:
    WYT的刷子
    小烈送菜
    猴腮雷
    基于Docker的Mysql主从复制搭建
    C#集合类型大揭秘
    ASP.NET三剑客 HttpApplication HttpModule HttpHandler 解析
    使用缓存的正确姿势
    【模块化那些事】 拆散的模块化
    分享一个开源的网盘下载工具BaiduPCS-Go
    【抽象那些事】不必要的抽象
  • 原文地址:https://www.cnblogs.com/IMyLife/p/15191267.html
Copyright © 2011-2022 走看看