问题引出  

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

  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. VS编译代码未通过,常见问题。

    问题一:LNK2028 这个问题一般是什么函数在哪里被引用.修改的方法是:先检查是否包含头文件,如果已经包含了头文件,则检查在源文件的"import.cpp"中是否包含了该lib文 ...

  3. BZOJ_4590_[Shoi2015]自动刷题机_二分答案

    BZOJ_4590_[Shoi2015]自动刷题机_二分答案 Description 曾经发明了信号增幅仪的发明家SHTSC又公开了他的新发明:自动刷题机--一种可以自动AC题目的神秘装置.自动 刷题 ...

  4. java后台验证码工具

    jcaptcha和kaptcha是两个比较常用的图片验证码生成工具,功能强大.kaptcha是google公司制作,Jcaptcha是CAPTCHA里面的一个比较著名的项目. Shiro 结合 kca ...

  5. QTimer在QThread环境中失效的问题

    QTimer在非QThread的环境下能正常工作.但在QThread环境下,需要做一些改动才能正常工作. 创建Qt的线程有两种方式: 1. 子例化QThread 可以在虚函数run中启动定时器,大致的 ...

  6. ASP.Net Core Razor+AdminLTE 小试牛刀

    AdminLTE 一个基于 bootstrap 的轻量级后台模板,这个前端界面个人感觉很清爽,对于一个大后端的我来说,可以减少较多的时间去承担前端的工作但又必须去独立去完成一个后台系统开发的任务,并且 ...

  7. 跳动在网页中间的精灵----Javascript

    今天开始js的内容整理,跳动在网页里的精灵就是它了. 一.简介 1.什么是Javascript JavaScript 是一种具有面向对象能力的.解释型的程序设计语言.更具体一点,它是基于对象和事件驱动 ...

  8. 打造自己的Android常用知识体系

    前言 Android常用知识体系是什么鬼?所谓常用知识体系,就是指对项目中重复使用率较高的功能点进行梳理.注意哦,不是Android知识体系. 古语道:学而不思则罔,思而不学则殆.如果将做项目类比为“ ...

  9. mvc、mvp和mvvm

    一.MVC 设计图: 可能由于MVP.MVVM的兴起,MVC在android中的应用变得越来越少了,但MVC是基础,理解好MVC才能更好的理解MVP,MVVM.因为后两种都是基于MVC发展而来的. 1 ...

  10. 《k8s-1.13版本源码分析》-调度预选

    本文大纲 预选流程 predicate的并发 一个node的predicate predicates的顺序 单个predicate执行过程 具体的predicate函数 本系列文章已经开源到githu ...