Skip to content

ApiCode 定义

参考来源

定义规范

语法标准

以下来源自 Java开发手册(黄山版)

  1. 【强制】错误码的制定原则:快速溯源、沟通标准化。
    • 说明:错误码想得过于完美和复杂,就像康熙字典的生僻字一样,用词似乎精准,但是字典不容易随身携带且简单易懂。
    • 正例:错误码回答的问题是谁的错?错在哪?
      1. 错误码必须能够快速知晓错误来源,可快速判断是谁的问题。
      2. 错误码必须能够进行清晰地比对(代码中容易 equals)。
      3. 错误码有利于团队快速对错误原因达到一致认知。
  2. 【强制】错误码不体现版本号和错误等级信息。
    • 说明:错误码以不断追加的方式进行兼容。错误等级由日志和错误码本身的释义来决定。
  3. 【强制】全部正常,但不得不填充错误码时返回五个零:00000。
  4. 【强制】错误码为字符串类型,共 5 位,分成两个部分:错误产生来源+四位数字编号。
    • 说明:错误产生来源分为 A/B/C,
      • A 表示错误来源于用户,比如参数错误,用户安装版本过低,用户支付超时等问题;
      • B 表示错误来源于当前系统,往往是业务逻辑出错,或程序健壮性差等问题;
      • C 表示错误来源于第三方服务,比如 CDN 服务出错,消息投递超时等问题;
      • 四位数字编号从 0001 到 9999,大类之间的步长间距预留 100,参考参考值表
  5. 【强制】编号不与公司业务架构,更不与组织架构挂钩,以先到先得的原则在统一平台上进行,审批生效,编号即被永久固定。
  6. 【强制】错误码使用者避免随意定义新的错误码。
    • 说明:尽可能在原有错误码附表中找到语义相同或者相近的错误码在代码中使用即可。
  7. 【强制】错误码不能直接输出给用户作为提示信息使用。
    • 说明:堆栈(stack_trace)、错误信息(error_message) 、错误码(error_code)、提示信息(user_tip)是一个有效关联并互相转义的和谐整体,但是请勿互相越俎代庖。
  8. 【推荐】错误码之外的业务信息由 error_message 来承载,而不是让错误码本身涵盖过多具体业务属性。
  9. 【推荐】在获取第三方服务错误码时,向上抛出允许本系统转义,由 C 转为 B,并且在错误信息上带上原有的第三方错误码。
  10. 【参考】错误码分为一级宏观错误码、二级宏观错误码、三级宏观错误码。
    • 说明:在无法更加具体确定的错误场景中,可以直接使用一级宏观错误码,分别是:A0001(用户端错误)、B0001(系统执行出错)、C0001(调用第三方服务出错)。
    • 正例:调用第三方服务出错是一级,中间件错误是二级,消息服务出错是三级。
  11. 【参考】错误码的后三位编号与 HTTP 状态码没有任何关系。
  12. 【参考】错误码有利于不同文化背景的开发者进行交流与代码协作。
    • 说明:英文单词形式的错误码不利于非英语母语国家(如阿拉伯语、希伯来语、俄罗斯语等)之间的开发者互相协作。
  13. 【参考】错误码即人性,感性认知+口口相传,使用纯数字来进行错误码编排不利于感性记忆和分类。
    • 说明:数字是一个整体,每位数字的地位和含义是相同的。
    • 反例:一个五位数字 12345,第 1 位是错误等级,第 2 位是错误来源,345 是编号,人的大脑不会主动地拆开并分辨每位数字的不同含义。

参考值表

以下来源自 Java开发手册(黄山版)

错误码中文描述说明
0一切 ok正确执行后的返回
A0001用户端错误一级宏观错误码
A0100用户注册错误二级宏观错误码
A0101用户未同意隐私协议
A0102注册国家或地区受限
A0110用户名校验失败
A0111用户名已存在
A0112用户名包含敏感词
A0113用户名包含特殊字符
A0120密码校验失败
A0121密码长度不够
A0122密码强度不够
A0130校验码输入错误
A0131短信校验码输入错误
A0132邮件校验码输入错误
A0133语音校验码输入错误
A0140用户证件异常
A0141用户证件类型未选择
A0142大陆身份证编号校验非法
A0143护照编号校验非法
A0144军官证编号校验非法
A0150用户基本信息校验失败
A0151手机格式校验失败
A0152地址格式校验失败
A0153邮箱格式校验失败
A0200用户登录异常二级宏观错误码
A0201用户账户不存在
A0202用户账户被冻结
A0203用户账户已作废
A0210用户密码错误
A0211用户输入密码错误次数超限
A0220用户身份校验失败
A0221用户指纹识别失败
A0222用户面容识别失败
A0223用户未获得第三方登录授权
A0230用户登录已过期
A0240用户验证码错误
A0241用户验证码尝试次数超限
A0300访问权限异常二级宏观错误码
A0301访问未授权
A0302正在授权中
A0303用户授权申请被拒绝
A0310因访问对象隐私设置被拦截
A0311授权已过期
A0312无权限使用 API
A0320用户访问被拦截
A0321黑名单用户
A0322账号被冻结
A0323非法 IP 地址
A0324网关访问受限
A0325地域黑名单
A0330服务已欠费
A0340用户签名异常
A0341RSA 签名错误
A0400用户请求参数错误二级宏观错误码
A0401包含非法恶意跳转链接
A0402无效的用户输入
A0410请求必填参数为空
A0411用户订单号为空
A0412订购数量为空
A0413缺少时间戳参数
A0414非法的时间戳参数
A0420请求参数值超出允许的范围
A0421参数格式不匹配
A0422地址不在服务范围
A0423时间不在服务范围
A0424金额超出限制
A0425数量超出限制
A0426请求批量处理总个数超出限制
A0427请求 JSON 解析失败
A0430用户输入内容非法
A0431包含违禁敏感词
A0432图片包含违禁信息
A0433文件侵犯版权
A0440用户操作异常
A0441用户支付超时
A0442确认订单超时
A0443订单已关闭
A0500用户请求服务异常二级宏观错误码
A0501请求次数超出限制
A0502请求并发数超出限制
A0503用户操作请等待
A0504WebSocket 连接异常
A0505WebSocket 连接断开
A0506用户重复请求
A0600用户资源异常二级宏观错误码
A0601账户余额不足
A0602用户磁盘空间不足
A0603用户内存空间不足
A0604用户 OSS 容量不足
A0605用户配额已用光蚂蚁森林浇水数或每天抽奖数
A0700用户上传文件异常二级宏观错误码
A0701用户上传文件类型不匹配
A0702用户上传文件太大
A0703用户上传图片太大
A0704用户上传视频太大
A0705用户上传压缩文件太大
A0800用户当前版本异常二级宏观错误码
A0801用户安装版本与系统不匹配
A0802用户安装版本过低
A0803用户安装版本过高
A0804用户安装版本已过期
A0805用户 API 请求版本不匹配
A0806用户 API 请求版本过高
A0807用户 API 请求版本过低
A0900用户隐私未授权二级宏观错误码
A0901用户隐私未签署
A0902用户摄像头未授权
A0903用户相机未授权
A0904用户图片库未授权
A0905用户文件未授权
A0906用户位置信息未授权
A0907用户通讯录未授权
A1000用户设备异常二级宏观错误码
A1001用户相机异常
A1002用户麦克风异常
A1003用户听筒异常
A1004用户扬声器异常
A1005用户 GPS 定位异常
B0001系统执行出错一级宏观错误码
B0100系统执行超时二级宏观错误码
B0101系统订单处理超时
B0200系统容灾功能被触发二级宏观错误码
B0210系统限流
B0220系统功能降级
B0300系统资源异常二级宏观错误码
B0310系统资源耗尽
B0311系统磁盘空间耗尽
B0312系统内存耗尽
B0313文件句柄耗尽
B0314系统连接池耗尽
B0315系统线程池耗尽
B0320系统资源访问异常
B0321系统读取磁盘文件失败
C0001调用第三方服务出错一级宏观错误码
C0100中间件服务出错二级宏观错误码
C0110RPC 服务出错
C0111RPC 服务未找到
C0112RPC 服务未注册
C0113接口不存在
C0120消息服务出错
C0121消息投递出错
C0122消息消费出错
C0123消息订阅出错
C0124消息分组未查到
C0130缓存服务出错
C0131key 长度超过限制
C0132value 长度超过限制
C0133存储容量已满
C0134不支持的数据格式
C0140配置服务出错
C0150网络资源服务出错
C0151VPN 服务出错
C0152CDN 服务出错
C0153域名解析服务出错
C0154网关服务出错
C0200第三方系统执行超时二级宏观错误码
C0210RPC 执行超时
C0220消息投递超时
C0230缓存服务超时
C0240配置服务超时
C0250数据库服务超时
C0300数据库服务出错二级宏观错误码
C0311表不存在
C0312列不存在
C0321多表关联中存在多个相同名称的列
C0331数据库死锁
C0341主键冲突
C0400第三方容灾系统被触发二级宏观错误码
C0401第三方系统限流
C0402第三方功能降级
C0500通知服务出错二级宏观错误码
C0501短信提醒服务失败
C0502语音提醒服务失败
C0503邮件提醒服务失败

实现代码样例

使用编译时枚举类实现

  • 简单,易用,不可自定义规则,系统编译时校验

  • ApiCodeByEnumerate.java

    java
    @Getter
    public enum ApiCodeByEnumerate {
        // 正确执行
        SUCCESS("00000", "正确执行", "Success"),
        // 用户端错误 一级宏观错误码
        USER_SIDE_ERROR("A0001", "用户端错误", "User-side error"),
        // 用户注册错误 二级宏观错误码
        USER_SIDE_REGISTER_ERROR("A0100", "用户注册错误 ", "User-side register error"),
        // 用户未同意隐私协议
        USER_SIDE_REGISTER_NOT_AGREE_ARGEEMENT_ERROR("A0101", "用户未同意隐私协议 ", "User do not agree the Privacy Agreement");
    
        private String code;
        private String msgZh;
        private String msgEn;
    
        ApiCodeByEnumerate(String code, String msgZh, String msgEn) {
            this.code = code;
            this.msgZh = msgZh;
            this.msgEn = msgEn;
        }
    }
  • Main.java

    java
    public class Main {
        public static void main(String[] args) {
            // 00000
            System.out.println(ApiCodeByEnumerate.SUCCESS.getCode());
            // 正确执行
            System.out.println(ApiCodeByEnumerate.SUCCESS.getMsgZh());
            // Success
            System.out.println(ApiCodeByEnumerate.SUCCESS.getMsgEn());
        }
    }

使用运行时枚举类实现

[《Java 开发手册(泰山版)》定义了统一的错误码方案,是不是太理想化了? - Feego 的回答 - 知乎 https://www.zhihu.com/question/389789766/answer/1179593687 > feego-common/feego-common-web/feego-common-web/src/main/java/io/github/lvyahui8/web/code at master · lvyahui8/feego-common

  • 复杂,有一定扩展性,可自定义规则,运行时校验

  • ApiCodeByAnnotation.java

    java
    public interface ApiCodeByAnnotation {
        public enum General implements MsgCode {
            @ApiCode(code = "00000", msgZh = "成功", msgEn = "Success") SUCCESS,
        }
    
        @ApiCodePrefix({"A"})
        public enum USER_SIDE implements MsgCode {
            // Leave 1
            @ApiCode(code = "0001", msgZh = "用户端错误", msgEn = "User-side error") UNIVERSAL_ERROR,
            // Leave 2
            @ApiCode(code = "0100", msgZh = "用户注册错误", msgEn = "User-side register error") REGISTER_ERROR,
        }
    
        @ApiCodePrefix({"A", "01"})
        public enum USER_SIDE_REGISTER implements MsgCode {
            @ApiCode(code = "01", msgZh = "用户未同意隐私协议", msgEn = "User do not agree the Privacy Agreement") NOT_AGREE_ARGEEMENT_ERROR,
        }
    }
    • Main.java

      java
      public class Main {
          public static void main(String[] args) {
              // A0101
              System.out.println(ApiCodeByAnnotation.USER_SIDE_REGISTER.NOT_AGREE_ARGEEMENT_ERROR.getCode());
              // 用户未同意隐私协议
              System.out.println(ApiCodeByAnnotation.USER_SIDE_REGISTER.NOT_AGREE_ARGEEMENT_ERROR.getMsgZh());
              // User do not agree the Privacy Agreement
              System.out.println(ApiCodeByAnnotation.USER_SIDE_REGISTER.NOT_AGREE_ARGEEMENT_ERROR.getMsgEn());
          }
      }
  • ApiCode.java (定义编码注解)

    java
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ApiCode {
        String code();
        String msgZh();
        String msgEn();
    }
  • ApiCodePrefix.java (定义编码前缀注解)

    java
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ApiCodePrefix {
        String[] value();
    }
  • Code.java (定义枚举包含字段)

    java
    @Data
    class Code {
        private String code;
        private String msgZh;
        private String msgEn;
    }
  • MsgCode.java (通用接口)

    java
    public interface MsgCode {
        default String getCode() {
            return CodeRepository.get(this).getCode();
        }
        default String getMsgZh() {
            return CodeRepository.get(this).getMsgZh();
        }
        default String getMsgEn() {
            return CodeRepository.get(this).getMsgEn();
        }
    }
  • CodeRepository.java (核心注册类)

    java
    class CodeRepository {
        private static final Map<Object, Code> CODE_MAP = new ConcurrentHashMap<>();
    
        static Code get(Object object) {
            // 单例
            if (CODE_MAP.containsKey(object)) {
                return CODE_MAP.get(object);
            }
            try {
                Enum<?> codeEnum = (Enum<?>) object;
                // 获取前缀注解
                ApiCodePrefix apiCodePrefix = object.getClass().getAnnotation(ApiCodePrefix.class);
                // 获取注解
                ApiCode apiCode = object.getClass().getField(codeEnum.name()).getAnnotation(ApiCode.class);
    
                Code codeInst = new Code();
    
                // 拼接 Code
                StringBuffer codeStrBuf = new StringBuffer();
                if (apiCodePrefix != null) {
                    for (String item : apiCodePrefix.value()) {
                        codeStrBuf.append(item);
                    }
                }
                codeStrBuf.append(apiCode.code());
                codeInst.setCode(codeStrBuf.toString());
    
                // 设置提示文本
                codeInst.setMsgZh(apiCode.msgZh());
                codeInst.setMsgEn(apiCode.msgEn());
    
                CODE_MAP.put(object, codeInst);
                return codeInst;
            } catch (NoSuchFieldException e) {
                throw new Error(e);
            }
        }
    }