API介面通訊引數規範

wu發表於2019-02-14

  通常在很多的公司裡面,對於介面的返回值沒做太大規範,所以會比較常看到各個專案各自定義隨意的返回值,比如以下情況:

  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. 公司這麼多專案都這樣的話,如果某個系統需要新增一個提示或者英文不規範修改了,那會不會造成不一致呢?

 

  後續文章會針對這些問題和可能存在的問題進行探討!

相關文章