retrofit okhttp中token失效再次请求,包含response中返回的错误代码解析

只要用到请求,那么一定会用到token这个东西
这篇文章主要讲两个方面
1.token失效的处理
目标:我们一定不想在token失效后,直接提示用户重新登录获取新的token
解决步骤:自定义一个Interceptor,用于token失效的处理


import com.avicsafety.lib.tools.Validate;
import com.google.gson.Gson;

import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.nio.charset.Charset;

import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okio.Buffer;
import okio.BufferedSource;

public abstract class TokenInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Response response = chain.proceed(request);

        if (isTokenExpired(response)) {//根据和服务端的约定判断token过期
            //同步请求方式,获取最新的Token
            String newSession = getNewToken();
            if (Validate.isNotNull(newSession)) {
                //使用新的Token,创建新的请求
                Request newRequest = chain.request()
                        .newBuilder()
                        .header("access_token", newSession)
                        .build();
                //重新请求
                return chain.proceed(newRequest);
            }
        }
        return response;
    }

    /**
     * 根据Response,判断Token是否失效
     *
     * @param response
     * @return
     */
    private boolean isTokenExpired(Response response) {
        try {
            // 第一种,当header的content-length不确定的情况下会出错
//            ResponseBody responseBody = response.peekBody(1024 * 1024);//关键代码

            // 第二种
            ResponseBody responseBody = response.body();
            BufferedSource source = responseBody.source();
            source.request(Long.MAX_VALUE); // Buffer the entire body.
            Buffer buffer = source.buffer();
            Charset UTF8 = Charset.forName("UTF-8");
            String json = buffer.clone().readString(UTF8);

            // 第三种绝对不行,这个东西一次请求只有一次读取
//            BufferedSource source = responseBody.source();
//            String json = source.readString(Charset.defaultCharset());
            if (StringUtils.isNotBlank(json)) {
                Gson gson = new Gson();
                BaseResp baseResp = gson.fromJson(json, BaseResp.class);
                if (baseResp!=null && 400 == baseResp.getResCode()) {
                    return true;
                }
            }
        } catch (IOException e) {
            return false;
        }
        // 当返回码为400或者401的时候,我们认定token失效
        if (response.code() == 401 || response.code() == 400) {
            return true;
        }
        return false;
    }

    /**
     * 同步请求方式,获取最新的Token
     *
     * @return
     */
    public abstract String getNewToken() throws IOException;
}

我直接把自定义的HttpUtils也贴出来吧,方便大家使用

```java
package com.avicsafety.lib.OkHttp;


import com.avicsafety.lib.tools.AESUtils;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;

import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import okhttp3.OkHttpClient;
import okhttp3.ResponseBody;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Converter;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;

public class HttpUtils {
    private static HttpUtils retrofitUtils;
    private static Retrofit retrofit;
    private static Boolean debug = false;
    private static String hostName = "";
    private static String token = null;
    private static TokenExpiredListener tokenExpiredListener = null;

    private HttpUtils() {

    }

    public TokenExpiredListener getTokenExpiredListener() {
        return tokenExpiredListener;
    }

    public void setTokenExpiredListener(TokenExpiredListener tokenExpiredListener) {
        HttpUtils.tokenExpiredListener = tokenExpiredListener;
    }

    /**
     * 建议在MyApplication中 初始化
     *
     * @param _debug
     */
    public void init(String _hostName, Boolean _debug) {
        retrofit = null;
        debug = _debug;
        hostName = _hostName;
    }

    /**
     * 提供服务器地址系信息
     * @return
     */
    public String getUrl() {
        return hostName;
    }

    /**
     * 设置 token 访问时可以携带此token
     *
     * @param username
     * @param password
     * @param key
     */
    public void setAuthToken(String username, String password, String key) {
        reset();
        key = AESUtils.initKey(key);
        String _token = AESUtils.encrypt(username + "@,@" + password, key);
        token = _token;
    }

    /**
     * 设置 token 访问时可以携带此token
     *
     * @param _token token
     */
    public void setAuthToken(String _token) {
        reset();
        token = _token;
    }

    /**
     * 重新设置 token 和 核心工具
     */
    public void reset() {
        retrofit = null;
        token = null;
    }

    public static HttpUtils getInstance() {

        if (retrofitUtils == null) {
            synchronized (HttpUtils.class) {
                if (retrofitUtils == null) {
                    retrofitUtils = new HttpUtils();
                }
            }
        }
        return retrofitUtils;
    }

    public String getToken() {
        return token;
    }


    public static synchronized Retrofit getRetrofit() {
        if (retrofit == null) {

            HttpLoggingInterceptor logInterceptor = new HttpLoggingInterceptor();
            if (debug) {
                //显示日志
                logInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
            } else {
                logInterceptor.setLevel(HttpLoggingInterceptor.Level.NONE);
            }

            HeaderInterceptor headerInterceptor = new HeaderInterceptor(token);
            TokenInterceptor tokenInterceptor = new TokenInterceptor() {
                @Override
                public String getNewToken() throws IOException {
                    if (tokenExpiredListener != null) {
                        return tokenExpiredListener.getNewToken();
                    }
                    return null;
                }
            };
            OkHttpClient httpClient = new OkHttpClient.Builder()
                    .addInterceptor(logInterceptor)
                    .addInterceptor(headerInterceptor)
                    .addInterceptor(tokenInterceptor)
                    //配置SSlSocketFactory
//                    .sslSocketFactory(SSLSocketFactoryUtils.createSSLSocketFactory(), SSLSocketFactoryUtils.createTrustAllManager())
//                    .hostnameVerifier(new SSLSocketFactoryUtils.TrustAllHostnameVerifier())
                    .connectTimeout(60, TimeUnit.SECONDS)
                    .readTimeout(120, TimeUnit.SECONDS)
                    .writeTimeout(60, TimeUnit.SECONDS).build();
            Gson gson = new GsonBuilder()
                    //解决map Double 问题
                    .registerTypeAdapter(Map.class,
                            new JsonDeserializer<Map<String, Object>>() {
                                @Override
                                public Map<String, Object> deserialize(JsonElement json, Type typeOfT,
                                                                       JsonDeserializationContext context) throws JsonParseException {
                                    Map treeMap = new HashMap<>();
                                    JsonObject jsonObject = json.getAsJsonObject();
                                    Set<Map.Entry<String, JsonElement>> entrySet = jsonObject.entrySet();
                                    for (Map.Entry<String, JsonElement> entry : entrySet) {
                                        if (entry.getValue().isJsonArray()) {
                                            treeMap.put(entry.getKey(), entry.getValue());
                                        } else {
                                            treeMap.put(entry.getKey(), entry.getValue().getAsString());
                                        }
                                    }
                                    return treeMap;
                                }
                            })
                    .registerTypeAdapter(Date.class, new DateAdapterNull())
                    //解决 日期格式问题
                    .setDateFormat("yyyy-MM-dd HH:mm:ss")
                    .create();

            retrofit = new Retrofit.Builder().baseUrl(hostName).client(httpClient)
                    .addConverterFactory(new NullOnEmptyConverterFactory())
                    .addConverterFactory(GsonConverterFactory.create(gson))
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .build();

//            retrofit = new Retrofit.Builder().baseUrl(hostName).client(httpClient).addConverterFactory(JacksonConverterFactory.create())
//                    .build();

        }
        return retrofit;
    }

    public static class NullOnEmptyConverterFactory extends Converter.Factory {

        @Override
        public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
            final Converter<ResponseBody, ?> delegate = retrofit.nextResponseBodyConverter(this, type, annotations);
            return new Converter<ResponseBody,Object>() {
                @Override
                public Object convert(ResponseBody body) throws IOException {
                    if (body.contentLength() == 0) return null;
                    return delegate.convert(body);
                }
            };
        }
    }


    /***
     * 读取*.cer公钥证书文件, 获取公钥证书信息
     * @author xgh
     */
    public static void testReadX509CerFile(InputStream inStream) throws Exception {

        try {
            // 读取证书文件

            // 创建X509工厂类
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            // 创建证书对象
            X509Certificate oCert = (X509Certificate) cf
                    .generateCertificate(inStream);
            inStream.close();
            SimpleDateFormat dateformat = new SimpleDateFormat("yyyy/MM/dd");
            String info = null;
            // 获得证书版本
            info = String.valueOf(oCert.getVersion());
            System.out.println("证书版本:" + info);
            // 获得证书序列号
            info = oCert.getSerialNumber().toString(16);
            System.out.println("证书序列号:" + info);
            // 获得证书有效期
            Date beforedate = oCert.getNotBefore();
            info = dateformat.format(beforedate);
            System.out.println("证书生效日期:" + info);
            Date afterdate = oCert.getNotAfter();
            info = dateformat.format(afterdate);
            System.out.println("证书失效日期:" + info);
            // 获得证书主体信息
            info = oCert.getSubjectDN().getName();
            System.out.println("证书拥有者:" + info);
            // 获得证书颁发者信息
            info = oCert.getIssuerDN().getName();
            System.out.println("证书颁发者:" + info);
            // 获得证书签名算法名称
            info = oCert.getSigAlgName();
            System.out.println("证书签名算法:" + info);

        } catch (Exception e) {
            System.out.println("解析证书出错!");
            e.printStackTrace();
        }
    }

    private static class DateAdapterNull implements JsonSerializer {
        //数据返回时date转成json字符

        @Override
        public JsonElement serialize(Object src, Type typeOfSrc, JsonSerializationContext context) {
            if (src == null) {
                return new JsonPrimitive("");
            } else {
                DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                return new JsonPrimitive(formatter.format(src));
            }
        }
    }

    public <T> T getApiService(Class<T> clazz) {
        Retrofit retrofit = getRetrofit();
        return retrofit.create(clazz);
    }


    public interface TokenExpiredListener {

        String getNewToken() throws IOException;
    }
}


代码不多,我也就不一一讲解了
这里主要说一下第二点问题,就是这个token失效被服务器端定义在内部业务代码中,怎么办呢
办法很简单,我们直接取出来,判断一下就可以了
嗯嗯,道理是这样的,但当你从response中直接拿出ResponseBody时,你会发现错误出现了

End of input at line 1 column 1 path $

为什么呢,OkHttp不把它存储在内存中,就是你需要的时候就去读一次 只给你了内容,没有给引用,所以一次请求读一次,打印body后原ResponseBody会被清空,response中的流会被关闭,程序会报错,我们需要创建出一个新的ResponseBody 给应用层处理。

那么我们就会用到以下代码

try {
            // 第一种,当header的content-length不确定的情况下会出错
//            ResponseBody responseBody = response.peekBody(1024 * 1024);//关键代码

            // 第二种
            ResponseBody responseBody = response.body();
            BufferedSource source = responseBody.source();
            source.request(Long.MAX_VALUE); // Buffer the entire body.
            Buffer buffer = source.buffer();
            Charset UTF8 = Charset.forName("UTF-8");
            String json = buffer.clone().readString(UTF8);

            // 第三种绝对不行,这个东西一次请求只有一次读取
//            BufferedSource source = responseBody.source();
//            String json = source.readString(Charset.defaultCharset());
            if (StringUtils.isNotBlank(json)) {
                Gson gson = new Gson();
                BaseResp baseResp = gson.fromJson(json, BaseResp.class);
                if (baseResp!=null && 400 == baseResp.getResCode()) {
                    return true;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页