问题引出  

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

  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 = , /// <summary>
/// 操作失败
///</summary>
[Display(Name = "操作失败")]
Fail = , /// <summary>
/// 登陆失败
///</summary>
[Display(Name = "登陆失败")]
LoginFail = , /// <summary>
/// 没有该数据
///</summary>
[Display(Name = "没有数据")]
NoRecord = , /// <summary>
/// 用户不存在
///</summary>
[Display(Name = "用户不存在")]
NoSuchUser = , /// <summary>
/// 未登录
///</summary>
[Display(Name = "未登录")]
Unauthorized = , /// <summary>
/// 未授权
/// </summary>
[Display(Name = "未授权")]
Forbidden = , /// <summary>
/// 无效Token
/// </summary>
[Display(Name = "无效Token")]
InvalidToken = , /// <summary>
/// 参数验证失败
/// </summary>
[Display(Name = "参数验证失败")]
InvalidData = , /// <summary>
/// 无效用户
/// </summary>
[Display(Name = "无效用户")]
InvalidUser =
}

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

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

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

API接口通讯参数规范的更多相关文章

  1. API接口通讯参数规范(2)

    针对[API接口通讯参数规范]这篇文章留下的几个问题进行探讨. 问题1 试想一下,如果一个http请求返回一个500给我们,那我们是不是都不用看详情都知道该次请求发生了什么?这正是一个标准的结果码意义 ...

  2. Restful API 接口设计标准及规范

    Restful API 接口设计标准以及规范 RESTful概念 理解和评估以网络为基础的应用软件的架构设计,得到一个功能强.性能好.适宜通信的架构.REST指的是一组架构约束条件和原则." ...

  3. API接口防止参数篡改和重放攻击

    {近期领导要求我对公司业务的支付类的ocr接口做研究,是否存在支付接口重放攻击,so.....} API重放攻击(Replay Attacks)又称重播攻击.回放攻击.他的原理就是把之前窃听到的数据原 ...

  4. RESTful API接口文档规范小坑

    希望给你3-5分钟的碎片化学习,可能是坐地铁.等公交,积少成多,水滴石穿,谢谢关注. 前后端分离的开发模式,假如使用的是基于RESTful API的七层通讯协议,在联调的时候,如何避免配合过程中出现问 ...

  5. 如何写出安全的API接口(参数加密+超时处理+私钥验证+Https)- 续(附demo)

    上篇文章说到接口安全的设计思路,如果没有看到上篇博客,建议看完再来看这个. 通过园友们的讨论,以及我自己查了些资料,然后对接口安全做一个相对完善的总结,承诺给大家写个demo,今天一并放出. 对于安全 ...

  6. Spring框架学习笔记(9)——API接口设计相关知识及具体编码实现

    最近需要设计一个API服务器,想要把API接口搞得规范一下,就通过网上搜集到了一些资料,以下便是自己的一些理解以及相关的具体实现 本文采用的是spring boot+maven的方案 restful规 ...

  7. API 接口开发规范

    整体规范建议采用RESTful 方式来实施. 协议 API与用户的通信协议,总是使用HTTPs协议,确保交互数据的传输安全. 域名 应该尽量将API部署在专用域名之下.https://api.exam ...

  8. day71:drf:API接口&Restful API规范&Django Rest Framework&drf中的序列化和反序列化功能

    目录 1.web应用模式 2.API接口 3.Restful API规范 4.序列化 5.Django Rest Framework 1.drf的简单介绍 2.drf的特点 3.如何安装drf 4.d ...

  9. 后端API接口的错误信息返回规范

    前言 最近我司要制定开发规范.在讨论接口返回的时候,后端的同事询问我们前端,错误信息的返回,前端有什么意见? 所以做了一些调研给到后端的同事做参考. 错误信息返回 在使用API时无可避免地会因为各种情 ...

随机推荐

  1. vector作为函数返回类型

    在实际的操作中,我们经常会碰到需要返回一序列字符串或者一列数字的时候,以前会用到数组来保存这列的字符串或者数字,现在我们可以用vector来保存这些数据.但是当数据量很大的时候使用vector效率就比 ...

  2. java接口变量问题

    java中接口是不能实例化的,然而像下面这种用法是可以的: List<FileItem> items = upload.parseRequest(request); Iterator< ...

  3. Postman-----设置环境变量

    1.环境变量的作用域: 使用环境变量(可用于切换开发环境/生产环境.设置动态参数),有4个作用域(优先级由高到低):Global, Environment, Local, Data.同名变量会使用优先 ...

  4. C# Vista Command Link Control with Windows Forms

    using System; using System.Text; using System.Windows.Forms; using System.Runtime.InteropServices; u ...

  5. 【Java进阶】并发编程

    PS:整理自极客时间<Java并发编程> 1. 概述 三种性质 可见性:一个线程对共享变量的修改,另一个线程能立刻看到.缓存可导致可见性问题. 原子性:一个或多个CPU执行操作不被中断.线 ...

  6. Apache Mina -2

    我们可以了解到 mina是个异步通信框架,一般使用场景是服务端开发,长连接.异步通信使用mina是及其方便的.不多说,看例子. 本次mina 使用的例子是使用maven构建的,过程中需要用到的jar包 ...

  7. Spark学习之数据读取与保存总结(二)

    8.Hadoop输入输出格式 除了 Spark 封装的格式之外,也可以与任何 Hadoop 支持的格式交互.Spark 支持新旧两套Hadoop 文件 API,提供了很大的灵活性. 要使用新版的 Ha ...

  8. python中线程和进程(二)

    目录 线程同步 Event Lock RLock Condition Barrier semaphore GIL 线程同步 线程同步,即线程之间协同工作,一个线程访问某些数据时,其他线程不能访问这些数 ...

  9. Maven把项目依赖的所有jar包都打到同一个jar中

    目录 1 使用maven-shade-plugin 2 推荐: 使用maven-assembly-plugin 3 扩展: Maven安装本地jar包到本地仓库 4 扩展: 手动生成jar包 5 扩展 ...

  10. 关于 JavaScript 中的继承

    ES5 之前,继续是这样实现的 function Parent() { this.foo = function() { console.log('foo'); }; } Parent.prototyp ...