package com.diandaxia.common.utils;

import java.util.Date;

/**
 * by liyuxin 2019.12.16 更新
 * 统一返回格式
 */
public class ApiResponse<T> {

    /**
     * 此次HTTP请求任务的最终结果状态,即HTTP协议层面成功了,然后对应的业务层面的操作也成功了,完全成功。
     * 举例:若HTTP请求层面没有任何异常抛出,业务层面也完全成功了,则flag=true;
     * 业务层面的完全成功的意思为:用户名和密码完全正确,后续所有的操作完全正确没有问题。
     * 若用户名或密码错误那么就是业务层面的错误,若业务层面的错误则flag=false,
     * 重要:这是一个所有环节均全部成功的标记,方便前端使用。前端可以判断此标记,来判断请求的所有环节是否完全成功。
     */
    private Boolean flag;


    /**
     * 错误代码--标识。
     * 这个code的作用,仅仅是来标识出错的时候的错误代码。没有任何含义,并非网上说的HTTP协议状态码。
     * 服务端已经做了全局异常处理,服务端返回的协议状态码永远是200;不要再加status字段,毫无意义。
     * 这个仅仅是一个标识,没有任何意思。
     * 0:标识成功
     * 1:标识一般的业务错误
     * 2:标识一般的异常错误
     * 3.标识token过期,当然为了方便前端使用,boolean类型的tkexp也标识token是否过期
     * 4.带hurl帮助信息的错误
     * 5.外部的API调用返回错误标识,External Error,举例:调用淘宝的API返回了错误
     * 一般0,1,2,3,4,5就完全足够了;宗旨不是给用户友好的提示错误,宗旨是不给用户任何提示错误,直接默认全部帮他处理好。所以错误不细分。而是疯狂减少错误的几率。
     * 若依然有一些,其它的错误需真的需要特殊标识,方便前端使用的需要向我报备,我会加在一个枚举bean里;
     */
    private Integer code;


    /**
     * 错误详细信息标识;
     * 成功时为null;失败才需要理由。
     */
    private String msg;


    /**
     * 若成功往往有data数据
     * 示例:
     *         UserBase userBase = new UserBase();
     *         userBase.setUserName("李昱鑫");
     *         userBase.setUserPwd("123");
     *         userBase.setUserAge(100);
     *        return ResponseUtil.okWithData(userBase); 对应结果1:
     *        return ResponseUtil.okWithData(objectMapper.writeValueAsString(userBase)); 对应结果2:
     *
     * 结果1:{"flag":true,"code":0,"msg":"ok","data":{"userName":"李昱鑫","userPwd":"123","userAge":100},"tkexp":false,"hurl":null,"time":"2019-12-31 08:19:25"}
     * 结果2:{"flag":true,"code":0,"msg":"ok","data":"{\"userName\":\"李昱鑫\",\"userPwd\":\"123\",\"userAge\":100}","tkexp":false,"hurl":null,"time":"2019-12-31 08:21:40"}
     * 这个为Object不再为String,若为String的话,当数据量非常多的时候,会产生很多转义字符\,浪费网络流量,所以这里修改为object类型,其余的工作交给前端。
     * 使用方法要求如下:
     *   1.当是用到第三方的接口时的返回时,比如淘宝的API接口,返回值必须与文档上一致。那么需要传入字符串,淘宝的举例:return ResponseUtil.okWithData(rsp.getBody());
     *     虽然这样会产生很多的转义字符,但是没有办法,必须与文档的返回一致,这时data要传入String类型。
     *   2.当是我们自己调用我们自己的接口时,就没有必须传入String了,直接传入Object就可以了。举例:查询数据库中的记录,直接返回mybatis的mapper查询的结果list对象就可以,举例:
     *     Mapper.java里 -- List<JdpTbTrade> selectListByTids(String tids); Controller里 -- return ResponseUtil.okWithData(jdpTbTradeMapper.selectListByTids(tids));
     *     这样就减少了转义字符,前端调试看着也方便。
     */
    private T data;


    /**
     * token过期的标记,这个比较常用,因为各个子项目都依赖token,当token过期的时候需要自动重新登录,获取新的token
     * 故把这个token过期单独出来标志,此标志仅仅表示token是否过期;秘钥错误等问题,并不标识
     * tkexp为token_expire缩写。true 标识过期了 false标识正常,没有过期.
     */
    private Boolean tkexp;


    /**
     * 当遇到错误的时候,有一些特殊错误必须要给出一套解决方案 来指导用户按流程操作,可以利用这个help_url,客户端要
     * 提示用户打开这个链接进行查看相关教程,这个必须是一个url链接.
     */
    private String hurl;


    /**
     * 外部的业务错误标识,External Error 缩写:exterr 举例:调用淘宝的API,分2种错误:
     * 1.若是我们自己的业务异常,则报错,举例token的验证,以及从Redis里取accessToken 都有可能异常,
     *   这种错误是我们自己的业务错误,
     *
     * 2.若我们自己的业务无错误,调用淘宝的接口,由于其他原因淘宝会给返回错误,这类错误是外部错误,需要特殊标识一下
     *   方便客户端知道是外部的业务错误还是内部的业务错误。
     * -----------------------------------------------------
     * 以下是客户端需要做的:
     * 1.根据flag就知道是遇到错误了,还是完全正确了。
     *
     * 2.若是遇到错误了,再通过额外标识这个字段,来判断是平台的接口返回的错误,还是我们自己的内部业务错误。
     *
     *   a.若是平台返回的错误【msg 里会是一个平台返回的完整 json字符串】,那么需要客户端解析msg里的字符串,然后做提示或其它默认处理操作。
     *
     *   b.若是我们自己的业务错误【msg 里会是一个普通的字符串 非json】,比如token过期,那么就不需要解析msg里的字符串,直接提示这个字符串就行。
     *   ----------------------------------------------------
     *   第一种情况,我们自己的业务错误,内部错误。
     *
     * {
     *     "flag": false,
     *     "code": 3,
     *     "msg": "token expire please login again",
     *     "data": null,
     *     "tkexp": true,
     *     "hurl": null,
     *     "exterr": false,
     *     "time": "2020-01-12 16:41:24"
     * }
     *
     *
     * 第二中情况,淘宝的API 返回错误。
     *
     * {
     *     "flag": false,
     *     "code": 1,
     *     "msg": "{\"error_response\":{\"code\":27,\"msg\":\"Invalid session\",\"sub_code\":\"invalid-sessionkey\",\"sub_msg\":\"非法或过期的SessionKey参数,请使用有效的SessionKey参数\",\"request_id\":\"556il21uswlw\"}}",
     *     "data": null,
     *     "tkexp": false,
     *     "hurl": null,
     *     "exterr": true,
     *     "time": "2020-01-12 16:43:28"
     * }
     */
    private Boolean exterr;


    /**
     * 响应处理时间,方便以后根据报文的时间来判断错误,spring boot 项目不要忘记配置jackson
     * spring.jackson.time-zone=GMT+8
     * spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
     */
    private Date time;


    /**
     *用于内部构造
     */
    private ApiResponse(Boolean flag, Integer code, String msg, T data, Boolean tkexp, String hurl, Boolean exterr){
        this.flag = flag;
        this.code = code;
        this.msg = msg;
        this.data = data;
        this.tkexp = tkexp;
        this.hurl = hurl;
        this.exterr = exterr;
        this.time = new Date();
    }


    /**
     * Http请求全程无异常--->业务代码层面也成功,即完全响应成功,不带返回数据(这个也常用,msg加了一个success)
     */
    public static <T> ApiResponse<T> ok(){
        return new ApiResponse<T>(true, 0,"ok",null, false, null, false);
    }


    /**
     * Http请求全程无异常--->业务代码层面也成功,即完全响应成功,带返回数据(这个常用)
     */
    public static <T> ApiResponse<T> okWithData(T data){
        return new ApiResponse<T>(true, 0,"ok", data, false, null, false);
    }


    /**
     * Http请求全程无异常--->业务代码层面不成功(比如用户需要输入密码或缺少参数),即不能完全响应成功。算失败。
     * 这个最常用,错误代码默认为1,一般性业务错误。
     */
    public static <T> ApiResponse<T> error(String msg){
        return new ApiResponse<T>(false, 1, msg,null, false, null, false);
    }


    /**
     * Http请求有异常抛出--->走不到业务代码层面,即不能完全响应成功。
     * 这个方法不常用,主要放在 SpringBoot项目的 全局异常类里使用。
     * 把异常的信息传递进来.错误代码为2:标识一般的异常错误
     */
    public static <T> ApiResponse<T> errorException(String msg){
        return new ApiResponse<T>(false, 2, msg,null, false, null, false);
    }


    /**
     * token过期,单独出来供全局异常处理那里使用,要求每一个子项目,必须有一个全局异常处理类,且这个全局异常处理类里必须包含
     * 处理token过期的方法,错误代码为3.标识token过期,当然为了方便前端使用,boolean类型的tkexp也标识token是否过期
     */
    public static <T> ApiResponse<T> errorTokenExpire(){
        return new ApiResponse<T>(false, 3, "Authorization token is expired",null, true, null, false);
    }


    /**
     * 有一些特殊错误必须要给出一套解决方案 来指导用户按流程操作,可以利用这个help_url
     * 提示用户打开这个链接进行查看相关教程,这个是必须返回一个教程对应的URL
     * 错误代码默认为1,一般性业务错误。
     */
    public static <T> ApiResponse<T> errorWithHelp(String msg, String hurl){
        return new ApiResponse<T>(false, 4, msg,null, false, hurl, false);
    }


    /**
     * 调用各个电商平台的Api返回的错误,并非我们自身的业务错误,需要标识一下外部错误.
     */
    public static <T> ApiResponse<T> errorExternalApi(String msg){
        return new ApiResponse<T>(false, 5, msg,null, false, null, true);
    }


    /**
     * 【这个比较少用,只有特殊情况下,才会使用,而且要尽量少用,只有当需要前端根据code来做出【自动】反应时才会用到】
     * Http请求全程无异常--->业务代码层面不成功,但需要特殊标识一下code,方便前端判断code自动做出处理。这个方法最不常用,特殊标识的
     * 我的目标不是友好的提示错误,还是自动消掉错误,这个类里要尽可能少错误的定义.
     * 特殊情况下,某一些错误,需要特殊标识,方便前端判断 然后自动做出处理
     * 现实中实际场景发现,每一个接口返回的code的意义可能都不同,无法统一code;
     * 举例:abc.diandaxia.com/aaa 接口若返回 code=100 可能代表的意思是 哎哎哎;
     *      abc.diandaxia.com/bbb 接口也返回 code=100 他代码的意思是 哈哈哈;
     * 可见不同的接口会返回相同的code,代表不同的意思,具体意思由接口人自己规定了。
     * 但是有几个code是共有code,不可私自定义,比如0-5;这几个code不可私自定义使用。
     * @param code 不可以为0,1,2,3,4,5
     */
    public static <T> ApiResponse<T> errorOtherMark(Integer code, String msg){
        if(code.equals(0) || code.equals(1) || code.equals(2) || code.equals(3) || code.equals(4) || code.equals(5)){
            return new ApiResponse<T>(false, code, "code limit error",null, false, null, false);
        }
        return new ApiResponse<T>(false, code, msg,null, false, null, false);
    }


    /**
     * 【这个尽量少用,只有在一些特殊的情况下才使用】
     * 场景1:一个接口需要返回临时token,但即使返回临时token 整理登录流程也算失败,
     * 所以最终会是:{flag:false,code:123,msg:需要短信二次认证,data:tempToken},
     * 这样有中文提示,同时也带回了一个辅助数据,方便客户端做一些自动操作。
     * 思维上,既然有okWithData 就也应该有 errorWithData,但是errorWithData
     * 仅仅只能一些特殊的情况下,才允许使用,所以命名为:errorOtherMarkWithData
     * 其它的说明与errorOtherMark一致。
     * @param code 不可以为0,1,2,3,4,5
     */
    public static <T> ApiResponse<T> errorOtherMarkWithData(Integer code, String msg, T data){
        if(code.equals(0) || code.equals(1) || code.equals(2) || code.equals(3) || code.equals(4) || code.equals(5)){
            return new ApiResponse<T>(false, code, "code limit error",null, false, null, false);
        }
        return new ApiResponse<T>(false, code, msg, data, false, null, false);
    }


    //============================== 以下是setter和getter 方便实例使用 ==================================


    public Boolean getFlag() {
        return flag;
    }

    public void setFlag(Boolean flag) {
        this.flag = flag;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public Boolean getTkexp() {
        return tkexp;
    }

    public void setTkexp(Boolean tkexp) {
        this.tkexp = tkexp;
    }

    public String getHurl() {
        return hurl;
    }

    public void setHurl(String hurl) {
        this.hurl = hurl;
    }

    public Boolean getExterr() {
        return exterr;
    }

    public void setExterr(Boolean exterr) {
        this.exterr = exterr;
    }

    public Date getTime() {
        return time;
    }

    public void setTime(Date time) {
        this.time = time;
    }
}

 

开始测试自动推断能力:

 

 

{"flag":true,"code":0,"msg":"ok","data":null,"tkexp":false,"hurl":null,"exterr":false,"time":"2020-06-20 20:10:07"}

 

 

 

{
    "flag": true,
    "code": 0,
    "msg": "ok",
    "data": {
        "userId": null,
        "userName": "小李飞刀",
        "userPwd": null,
        "userStatus": null,
        "userType": null,
        "userVersion": null,
        "registerTime": null,
        "lastLoginTime": null,
        "fuwuId": null
    },
    "tkexp": false,
    "hurl": null,
    "exterr": false,
    "time": "2020-06-20 20:11:16"
}

 

 

{
    "flag": true,
    "code": 0,
    "msg": "ok",
    "data": [
        {
            "userId": null,
            "userName": "小李飞刀",
            "userPwd": null,
            "userStatus": null,
            "userType": null,
            "userVersion": null,
            "registerTime": null,
            "lastLoginTime": null,
            "fuwuId": null
        }
    ],
    "tkexp": false,
    "hurl": null,
    "exterr": false,
    "time": "2020-06-20 20:12:58"
}

 

 

 

 

 

{
    "flag": true,
    "code": 0,
    "msg": "ok",
    "data": [
        {
            "userId": null,
            "userName": "小李飞刀",
            "userPwd": null,
            "userStatus": null,
            "userType": null,
            "userVersion": null,
            "registerTime": null,
            "lastLoginTime": null,
            "fuwuId": null
        }
    ],
    "tkexp": false,
    "hurl": null,
    "exterr": false,
    "time": "2020-06-20 20:15:03"
}

 

 

统统都没有任何问题,全程不报任何泛型警告,完美无瑕。 非常的方便使用;可见 Java 泛型 牛逼啊。

posted on 2020-06-20 20:17  del88  阅读(132)  评论(0编辑  收藏  举报