API接口错误码设计最佳实践

简介
  • 服务器接口设计中最重要的环节之一便是接口错误码的定义了,通常情况下服务端会定义一些列错误码用以指示接口调用者或者用户进行正确的操作。例如接口参数确实、参数非法、无权限访问、用户身份认证信息过期等等类似反馈。

** 本文将以错误码的类型、易用性、易读性、简洁性等方面进行讲解,内容有**

  • 接口错误码是用来干嘛的?
  • 接口错误码有哪些类型?
  • 接口错误码的定义方式有哪些?
  • 设计的接口错误码需要具有哪些能力?
  • 设计接口错误码的最佳实践

原文:地址

1. 接口错误码是用来干嘛的?

接口错误码,顾名思义,肯定是调用接口失败后反馈给客户端的信息,一般我们调用第三方的开放接口会看到一些非常规范的错误码定义,如下:

  • 系统错误示例:
{
"code": 0x02001023,
"msg": "应用秘钥错误"
}
{
"code": 0x02001111,
"msg": "系统未知错误,请稍后重试!"
}
  • 复杂错误类型示例:
{
"code": 0x02001024,  
"msg": "请求参数非法",   
"data": {  
    "subErrors": [  
        {    
            "error": "NotEmpty",   
            "msg": "请求参数name为必填项"   
        },    
        {    
            "error": "NotNull",    
            "msg": "请求参数age为必填项"    
        }    
    ]    
  }
}
  • 其他错误响应示例
{
"code": 0x0210ffc1,    
"subCode": "LoginAccountNotFound",
"msg": "未找到该账户信息,请核实后再登录!"
}

通过上面示例的错误码得知,错误码的作用有

  1. 诱导接口调用者使用正确的调用方式
  2. 指示调用方依据不同的错误码做逻辑控制处理
  3. 指示用户,引导用户进行正确的操作
  4. 明确指示服务器接口处理异常信息,便于开发人员及时发现与排查

所以设计一个好用又方便的错误码体系很重要!

2. 接口错误码有哪些类型?

主要分为两大类,系统错误码和业务错误码

  • 系统错误码: 一些通用错误信息的定义,一般用于指示开发者正确的进行接口调用和告知调用者接口服务的状态信息。
  • 业务错误码:根据具体业务流程提示或诱导用户进行正确的操作,如用户登录时,账号密码输入错误,接口错误码和提示信息会引导用户重新检查账号密码的正确性并进行重试。
3. 接口错误码的定义方式有哪些?

我们做开发时经常会使用到别人提供的接口,比如百度开放平台、支付宝、微信公众号提供的开放API等等。他们都会提供一些系统错误码说明,常见的形式有:

  1. 一个数字型的code标识

    • 长度可控,且字段值比较短,可节省传输带宽
    • 可读性不高,看到错误码后需要参考文档才能知其含义
    • 一般都会通过数字的范围对code进行分段,用于标识不同子服务、业务等等
  2. 一个字符串code标识

    • 长度会比数值型code长
  • 可读性高,见错误码便知其意
  • 通过业务领域对应的名字来进行描述,例如: 服务名+操作类型+失败原因
4. 我设计的接口错误码需要具有哪些能力?

本文所设计的错误码需要兼顾以下几个特点

  • 可读性一定要高
  • 兼容性要好,要能支持常见的两种错误码类型,字符串和数值型都需要提供
  • 易于维护,错误码要便于维护
  • 易用性,错误码的定义要对开发人员友好,最好开发人员不需要关心错误码的值
  • 灵活可控,错误码可以手动指定,也可以自动进行维护,而且支持错误码自动分段和手动分段
5. 接口错误码的最佳实践
5. 1 错误码的格式和模式的选择
  • 错误码格式采用如下方式
{
"code": 0x0210ffc1,    
"strCode": "LoginAccountNotFound",
"msg": "未找到该账户信息,请核实后再登录!",
"data":{}
}
  • code: Long型错误码
  • strCode: 字符串类型错误码
  • msg: 描述信息
  • data: 正常响应内容,json对象,由具体业务接口进行灵活指定
  • 正常响应结构:
{
"code": 0,    
"data":{}
}
  • 错误响应结构(兼容模式):
{
"code": 0x0210ffc1,    
"strCode": "LoginAccountNotFound",
"msg": "未找到该账户信息,请核实后再登录!"
}
  • 错误响应结构(数值型模式):
{
"code": 0x0210ffc1,    
"msg": "未找到该账户信息,请核实后再登录!"
}
  • 错误响应结构(字符串型模式):
{
"code": "LoginAccountNotFound",    
"msg": "未找到该账户信息,请核实后再登录!"
}
5. 2 错误码的定义
  • 系统错误码的定义
 @Getter
 @AllArgsConstructor
 public enum ErrorCodes {
 	SUCCESS(0,"success"),//成功
 	SYSTEM_PARAM_LOST(0x06001000,"接口参数缺失"),
     //... 其他错误码的定义
     ;    
     private Long code;
 	private String msg;
     
 }    
  • 业务错误码的定义
 @Getter
 @AllArgsConstructor
 public enum BussinessError {	
 		
 	TEST_EXAMPLE_OLD_ERROR(0x06600001, "test.exampleOldError", "完整错误码示例")//手动指定错误码	
 	,LOGIN_ACCOUNT_NOT_EXISTS("login.accountNotExists", "用户登录账号【%s】不存在")//不指定数值型错误码,将会自动生成
      //... 其他错误码的定义
     ;
 	private Integer code;
 	private String strCode;
 	private String msg;
     private BussinessError(int code,String strCode){
         this.code=code;
 		this.strCode=strCode;		
 	}
 	
 	private BussinessError(String strCode, String msg){
 		this.strCode=strCode;
 		this.msg=msg;
 	}
 	
 	private BussinessError(String strCode){
 		this.strCode=strCode;
 	}
     /**
        * 获取参数化的msg值
     */
     public String getMsgParams(Object ...params){
 		if(this.msg!=null){
 			return String.format(this.msg, params);
 		}
 		return this.msg;
 	}
     
 }
5. 3 业务错误码的自动生成
  • 通过错误码枚举成员字段名的字符串生成唯一标识数值,能有效保证字符串错误码不重复
/**  
    * Title HashUtil.java  
    * Description  
    * @author danyuan
    * @date Nov 8, 2019
    * @version 1.0.0
    * site: www.danyuanblog.com
*/ 
package com.danyuanblog.common.util;

public class HashUtil {
	
	public static int ELFhash(String str)//思想就是一直杂糅,使字符之间互相影响
	{
	    int h = 0x0, g;
	    for(int i = 0 ; i < str.length() ; i++)
	    {
	        h = (h<<4) + str.charAt(i); //h左移4位,当前字符占8位,加到h中进行杂糅
	        if((g = h & 0xf0000000) != 0) //取h最左四位的值,若均为0,则括号中执行与否没区别,故不执行
	        {
	            h ^= g>>24; //用h的最左四位的值对h的右起5~8进行杂糅
	            h &= ~g;//清空h的最左四位
	        }
	    }
	    return h; //因为每次都清空了最左四位,最后结果最多也就是28位二进制整数,不会超int
	}
	
	public static int limitELFHash(String str , int min, int max)
	{
	    int k = ELFhash(str);
	    k = Math.abs(k - min);
	    int result = k % (max - min);	
	    result += min;	    
	    return result;
	}
}
  • 指定范围的错误码生成
/**  
    * Title ErrorCodeUtil.java  
    * Description  错误码工具包
    * @author danyuan
    * @date Nov 8, 2019
    * @version 1.0.0
    * site: www.danyuanblog.com
*/ 
package com.danyuanblog.common.util;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import lombok.extern.slf4j.Slf4j;

import com.danyuanblog.base.exception.ErrorCodes;
import com.danyuanblog.common.consts.BussinessErrors;
import com.danyuanblog.common.exception.ErrorCodeDto;

@Slf4j
public final class ErrorCodeUtil {
	private final static HashMap<String, Integer> codeMap = new HashMap<>();
  //定义错误码范围为: 0x02100000 ~ 0x02ffffff
	private final static Integer MIN_VALUE = 0x02100000;
	private final static Integer MAX_VALUE = 0x02ffffff;
	static {
		//生成错误码映射关系
		init();
	}
	
	/**
    	 * 初始化错误码映射关系
    	 * 
    	 * @author danyuan
	 */
	public static void init(){
		codeMap.clear();
		Integer code = 0;
		for(BussinessErrors error :BussinessErrors.values()){
			if(error.getOldCode() == null || (error.getOldCode() == 0)){
				code = HashUtil.limitELFHash(error.name(), ErrorCodeUtil.MIN_VALUE, ErrorCodeUtil.MAX_VALUE);
			}else{//兼容老版错误码
				code = error.getOldCode();
			}			
			if(codeMap.containsValue(code)){
				//说明产生了冲突,打印冲突信息
				log.error("业务错误码HASH值产生了冲突,请更新错误码名字再启动应用,错误码冲突如下:");
				log.error("存在值: {} = {}", error.name(), code);
				System.exit(-1);//直接退出应用
			} else {
				codeMap.put(error.name(), code);
				log.info("业务错误码:{} = {}", error.name(), "0x0" + Integer.toHexString(code).toUpperCase());
			}			
		}
	}
	
	public static Integer getCode(BussinessErrors error){
		return codeMap.get(error.name());
	}
	
	/**
    	 * 获取业务错误码集合
    	 * @return
    	 * @author danyuan
	 */
	public static List<ErrorCodeDto> getBussinessCodes(){
		List<ErrorCodeDto> list = new ArrayList<>();
		ErrorCodeDto dto = null;
		for(String key : codeMap.keySet()){
			BussinessErrors error = BussinessErrors.valueOf(key);
			dto = new ErrorCodeDto();
			dto.setError(error.getCode())
				.setMsg(error.getMsg())
				.setCode(codeMap.get(key))
				.setHexCode("0x0" + Integer.toHexString(codeMap.get(key)).toUpperCase());
			
			list.add(dto);
		}
		return list;
	}
	
	/**
    	 * 获取系统错误码集合
    	 * @return
    	 * @author danyuan
	 */
	public static List<ErrorCodeDto> getSystemCodes(){
		List<ErrorCodeDto> list = new ArrayList<>();
		ErrorCodeDto dto = null;
		for(ErrorCodes error : ErrorCodes.values()){			
			dto = new ErrorCodeDto();
			dto.setError(error.getMsgCode())
				.setCode(error.getCode())
				.setHexCode("0x0" + Integer.toHexString(error.getCode()).toUpperCase());
			
			list.add(dto);
		}
		return list;
	}
}

  • 错误码查询接口
/**  
* Title ErrorCodeController.java  
* Description  
* @author danyuan
* @date Nov 19, 2018
* @version 1.0.0
* site: www.danyuanblog.com
*/ 
package com.danyuanblog.api;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.danyuanblog.api.impl.response.ErrorCodeListResponse;
import com.danyuanblog.base.exception.BusinessException;
import com.danyuanblog.base.exception.ErrorCodes;
import com.danyuanblog.common.exception.ErrorCodeDto;
import com.danyuanblog.common.util.ErrorCodeUtil;
import com.danyuanblog.web.api.DictManager;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@Api(tags={"错误码服务模块"},value="相关接口")
public class ErrorCodeController   {
	
	@Autowired
	private DictManager dictManager;
	
	@GetMapping(value="errorCodeList",
			name="查询错误码列表")
	@ApiOperation(value="查询错误码列表", notes="查询错误码列表")
	public ErrorCodeListResponse errorCodeList(
			) throws BusinessException{
		List<ErrorCodeDto> systemCodes = ErrorCodeUtil.getSystemCodes();
		for(ErrorCodeDto error : systemCodes){
			error.setMsg(dictManager.get(error.getError(),"zh_CN"));			
		}
		return new ErrorCodeListResponse().setBussinessErrors(ErrorCodeUtil.getBussinessCodes())
			.setSystemErrors(systemCodes);
	}

}
@Data
@Accessors(chain = true)
@ApiModel(value="ErrorCodeDto",description="业务错误码参数")
public class ErrorCodeDto implements Serializable{

	/** 
	 *serialVersionUID
	 */
	private static final long serialVersionUID = 1L;
	@ApiModelProperty(
			value="业务错误码字典值",
			example="user.accountNotFound"
			)
	private String error;
	@ApiModelProperty(
			value="系统错误码整型值对应的十六进制值",
			example="0x06002007"
			)
	private String hexCode;
	@ApiModelProperty(
			value="系统错误码整型值对应的十进制值",
			example="100671495"
			)
	private Integer code;
	
	@ApiModelProperty(
			value="错误描述信息",
			example="该用户账号未找到!"
			)
	private String msg;
}
@Data
@Accessors(chain = true)
@ApiModel(value="ErrorCodeListResponse",description="获取系统错误码信息的返回参数")
public class ErrorCodeListResponse implements Serializable {

	/** 
	 *serialVersionUID
	 */
	private static final long serialVersionUID = 1L;
	
	@ApiModelProperty(
			value="业务错误列表"
			)
	private List<ErrorCodeDto> bussinessErrors;
	@ApiModelProperty(
			value="系统错误列表"
			)
	private List<ErrorCodeDto> systemErrors;
}

通过这个接口便可以获取整个服务器提供的所有错误码及其含义说明

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,您的问题是关于API接口文档的模板和最佳实践的。以下是一个基本的API接口文档模板和设计指南: 1. 接口概述 在这一部分,您需要简要描述API的功能和目的。这是您向其他开发人员解释API的机会,因此应该清晰、简洁、易于理解。 2. 接口定义 在这一部分,您需要列出API的所有端点和请求方法。对于每个端点,您需要在表格中提供以下信息: - 请求方法:GET、POST、PUT、DELETE等 - 端点路径:/users/{userId}/orders - 请求参数:每个请求参数的名称、类型、是否必需以及描述 - 响应:每个响应参数的名称、类型、描述以及是否必需 3. 请求和响应示例 在这一部分,您需要提供一个或多个请求和响应示例,以便其他开发人员了解API如何工作。示例应该尽可能详细,包括请求正文、响应正文、状态代码等。 4. 接口错误 在这一部分,您需要列出所有可能的API错误和错误状态代码。对于每个错误,您需要提供错误代码、名称和描述。 5. 安全性 在这一部分,您需要描述API如何实现安全性和数据隐私。这可以包括身份验证、授权、访问控制、数据加密等。 6. 版本控制 在这一部分,您需要描述API的版本控制策略。这可以包括如何处理旧版本的API、何时发布新版本以及如何通知其他开发人员。 以上是一个基本的API接口文档模板和设计指南,您可以根据需要进行修改和扩展。希望这对您有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值