zoukankan      html  css  js  c++  java
  • API接口通讯参数规范

      问题引出  

      通常在很多的公司里面,对于接口的返回值没做太大规范,所以会比较常看到各个项目各自定义随意的返回值,比如以下情况:

      1. 直接返回bool值(True或者False)

      2. 返回void,只要不是异常信息,默认成功

      3. 直接返回异常详情(这个非常不好,通过一些低级的异常,客户可以看到公司的一个技术水平)

      4. 返回多个值,还要使用 out 来添加返回参数

      5. 。。。

      对于项目数量稍微多点的公司来说,接手多个项目的同事估计要吐血,所以项目间的业务通信规范是很有必要的。

      解决方案

      结合个人项目经验,定义一个专门用来封装返回值信息的通用类,如下:   

        /// <summary>
        /// 返回结果
        /// </summary>
        public interface IResult
        {
            /// <summary>
            /// 结果状态码
            /// </summary>
            ResultCode Code { get; set; }
    
            /// <summary>
            /// 提示信息
            /// </summary>
            /// <example>操作成功</example>
            string Message { get; set; }
    
            /// <summary>
            /// 是否成功
            /// </summary>
            bool Success { get; }
        }
    
        /// <summary>
        /// 返回的附带泛型数据
        /// </summary>
        public interface IResult<TType> : IResult
        {
            /// <summary>
            /// 返回的附带数据
            /// </summary>
            TType Data { get; set; }
        }

       这个ResultCode是针对业务操作结果的自定义枚举,用来标志当前返回的一个业务结果

     public enum ResultCode
        {
            /// <summary>
            /// 操作成功
            ///</summary>
            [Display(Name = "操作成功")]
            Ok = 1,
    
            /// <summary>
            /// 操作失败
            ///</summary>
            [Display(Name = "操作失败")]
            Fail = 11,
    
            /// <summary>
            /// 登陆失败
            ///</summary>
            [Display(Name = "登陆失败")]
            LoginFail = 12,
    
            /// <summary>
            /// 没有该数据
            ///</summary>
            [Display(Name = "没有数据")]
            NoRecord = 13,
    
            /// <summary>
            /// 用户不存在
            ///</summary>
            [Display(Name = "用户不存在")]
            NoSuchUser = 14,
    
            /// <summary>
            /// 未登录
            ///</summary>
            [Display(Name = "未登录")]
            Unauthorized = 20,
    
            /// <summary>
            /// 未授权
            /// </summary>
            [Display(Name = "未授权")]
            Forbidden = 21,
    
            /// <summary>
            /// 无效Token
            /// </summary>
            [Display(Name = "无效Token")]
            InvalidToken = 22,
    
            /// <summary>
            /// 参数验证失败
            /// </summary>
            [Display(Name = "参数验证失败")]
            InvalidData = 23,
    
            /// <summary>
            /// 无效用户
            /// </summary>
            [Display(Name = "无效用户")]
            InvalidUser = 24
        }

       有了以上的接口,我们可以看一下具体实现  

    public class Result : IResult
        {
            private string _message;
    
            /// <summary>
            /// 是否成功
            /// </summary>
            public bool Success => Code == ResultCode.Ok;
    
             /// <summary>
            /// 结果码
            /// </summary>
            public ResultCode Code {get; set;}
    
             /// <summary>
            /// 提示信息
            /// </summary>
            public string Message
            {
                get { return _message ?? Code.DisplayName(); }
                set { _message = value; }
            }
           
            /// <summary>
            /// 返回结果,默认成功
            /// </summary>
            public Result()
            {
                Code = ResultCode.Ok;
            }
    
            /// <summary>
            /// 返回结果
            /// </summary>
            /// <param name="code">状态码</param>
            /// <param name="message">提示信息</param>
            public Result(ResultCode code, string message = null)
            {
                Code = code;
                Message = message;
            }        
        }    

      这里我们定义了实现类,注意默认的构造函数是返回成功的,这方便我们后面针对业务对这个返回结果再次进行扩展。细心的大家应该注意到了返回的提示信息,我们针对上面的自定义枚举的提示信息会进行显示,后面具体实现再看。先看一下我们的泛型返回结果的实现

        /// <summary>
        /// 返回结果
        /// </summary>
        public class Result<TType> : Result, IResult<TType>
        {
            /// <summary>
            /// cotr
            /// </summary>
            public Result()
            {
            }
    
            /// <summary>
            /// 返回结果
            /// </summary>
            public Result(TType data)
                : base(ResultCode.Ok)
            {
                Data = data;
            }
    
            /// <summary>
            /// 返回结果
            /// </summary>
            /// <param name="code">状态码</param>
            /// <param name="message">提示信息</param>
            public Result(ResultCode code, string message = null)
                : base(code, message)
            {
            }
    
            /// <summary>
            /// 返回结果
            /// </summary>
            public Result(ResultCode code, string message = null, TType data = default(TType))
                : base(code, message)
            {
                Data = data;
            }
    
            /// <summary>
            /// 返回业务数据
            /// </summary>
            public TType Data { get; set; }
        }

      好有了这些,我们在Result类中定义一些静态方法对结果进行封装,这样可以让我们在业务层进行快速的调用

            /// <summary>
            /// 返回指定 Code
            /// </summary>
            public static Result FromCode(ResultCode code, string message = null)
            {
                return new Result(code, message);
            }
            
             /// <summary>
            /// 返回错误信息
            /// </summary>
            public static Result FromError(string message, ResultCode code = ResultCode.Fail)
            {
                return new Result(code, message);
            }
            
            /// <summary>
            /// 返回成功
            /// </summary>
            public static Result Ok(string message = null)
            {
                return FromCode(ResultCode.Ok, message);
            }
    
            /// <summary>
            /// 返回指定 Code
            /// </summary>
            public static Result<T> FromCode<T>(ResultCode code, string message = null)
            {
                return new Result<T>(code, message);
            }
    
            /// <summary>
            /// 返回指定 Code和提示信息
            /// </summary>
            public static Result<T> FromCode<T>(ResultCode code, T data, string message = null)
            {
                return new Result<T>(code, message, data);
            }       
    
            /// <summary>
            /// 返回错误信息
            /// </summary>
            public static Result<T> FromError<T>(string message, ResultCode code = ResultCode.Fail)
            {
                return new Result<T>(code, message);
            }
    
            /// <summary>
            /// 返回数据
            /// </summary>
            public static Result<T> FromData<T>(T data)
            {
                return new Result<T>(data);
            }
    
            /// <summary>
            /// 返回数据和提示信息
            /// </summary>
            public static Result<T> FromData<T>(T data, string message)
            {
                return new Result<T>(ResultCode.Ok, message, data);
            }
            
            /// <summary>
            /// 返回成功
            /// </summary>
            public static Result<T> Ok<T>(T data)
            {
                return FromData(data);
            }

      好了有了上面这些,我们该如何调用呢?当我们需要直接返回成功时,我们可以这样  

    return Result.Ok();

      前端接收到的结果如下:

      当我们需要返回带有数据的结果时,我们可以这样:

        var list = new List<string>
                {
                    "lex1",
                    "lex2"
                };
        return Result.FromData(list);

       前端接收到的结果如下:

      当我们需要返回指定Code的时候,如下:

    return Result.FromCode(ResultCode.LoginFail);

       前端接收到的结果如下:

       我们可以看到上面的提示信息是我们在枚举上定义的信息,这是我们在Result类中对Message进行了Code.DisplayName(),思想很简单,就是对枚举进行了扩展,利用DisplayAttribute的公用方法显示信息,那我们怎么知道什么时候调用DisplayAttribute的合适方法呢?

      我们先定义一个类DisplayProperty,用来对应DisplayAttribute的各个属性

        public enum DisplayProperty
        {
            /// <summary>
            /// 名称
            /// </summary>
            Name,
    
            /// <summary>
            /// 短名称
            /// </summary>
            ShortName,
    
            /// <summary>
            /// 分组名称
            /// </summary>
            GroupName,
    
            /// <summary>
            /// 说明
            /// </summary>
            Description,
    
            /// <summary>
            /// 排序
            /// </summary>
            Order,
    
            /// <summary>
            /// 水印信息
            /// </summary>
            Prompt,
        }

      有了这个之后,我们的枚举扩展方法如下:

            /// <summary>
            /// 获取枚举说明
            /// </summary>
            public static string DisplayName(this Enum val)
            {
                return val.Display(DisplayProperty.Name) as string;
            }
    
            /// <summary>
            /// 获取枚举短名称说明
            /// </summary>
            public static string DisplayShortName(this Enum val)
            {
                return val.Display(DisplayProperty.ShortName) as string;
            }
    
            /// <summary>
            /// 获取枚举水印信息
            /// </summary>
            public static string DisplayPrompt(this Enum val)
            {
                return val.Display(DisplayProperty.Prompt) as string;
            }
    
            /// <summary>
            /// 获取枚举备注
            /// </summary>
            public static string DisplayDescription(this Enum val)
            {
                return val.Display(DisplayProperty.Description) as string;
            }    
    
            /// <summary>
            /// 获取枚举指定的显示内容
            /// </summary>
            public static object Display(this Enum val, DisplayProperty property)
            {
                var enumType = val.GetType();
    
                var str = val.ToString();
    
                if (enumType.GetAttribute<FlagsAttribute>() != null && str.Contains(","))
                {
                    var array = str.Split(",", StringSplitOptions.RemoveEmptyEntries).Select(o => o.Trim());
    
                    var result = array.Aggregate("", (s, s1) =>
                    {
                        var f = enumType.GetField(s1);
    
                        if (f != null)
                        {
                  //MethodInfo的扩展,方法在下面
    var text = f.Display(property); return s.IsNullOrEmpty() ? text.ToString() : $"{s},{text}"; } return s; }); return result.IsNullOrEmpty() ? null : result; } var field = enumType.GetField(str); if (field != null) { return field.Display(property); } return null; }

      再看针对MemberInfo的一个扩展,这里面就根据我们传入的DisplayProperty属性值调用了DisplayAttribute的对应方法

            /// <summary>
            /// 获取枚举指定的显示内容
            /// </summary>
            public static object Display(this MemberInfo memberInfo, DisplayProperty property)
            {
                if (memberInfo == null) return null;
    
                var display = memberInfo.GetAttribute<DisplayAttribute>();
    
                if (display != null)
                {
                    switch (property)
                    {
                        case DisplayProperty.Name:
                            return display.GetName();
                        case DisplayProperty.ShortName:
                            return display.GetShortName();
                        case DisplayProperty.GroupName:
                            return display.GetGroupName();
                        case DisplayProperty.Description:
                            return display.GetDescription();
                        case DisplayProperty.Order:
                            return display.GetOrder();
                        case DisplayProperty.Prompt:
                            return display.GetPrompt();
                    }
                }
    
                return null;
            }

      到此我们的这个业务通讯结果已经可以了,再细想,有几个问题需要我们解决的:

      1. ResultCode的意义?

      2. 公司这么多项目都这样的话,如果某个系统需要新增一个提示或者英文不规范修改了,那会不会造成不一致呢?

      后续文章会针对这些问题和可能存在的问题进行探讨!

    如果你觉得该文章还不错且有所收获,请右下角推荐一下,也可留下您的问题或建议,让我们共同进步。 原创博客请在转载时保留原文链接或者在文章开头加上本人博客地址!
  • 相关阅读:
    kubernetes
    dubbo
    前端控件+资源
    Gossip
    问题解决1
    react
    impala
    storm+Calcite
    ASP.NET页面传值与跳转
    经典FormsAuthenticationTicket 分析
  • 原文地址:https://www.cnblogs.com/lex-wu/p/10370402.html
Copyright © 2011-2022 走看看