瀝青項目--token會定期過期(Retrofit,動態代理,cookie解析)

特點:

登錄後,從header中獲取token,存到本地,用於後續的請求。

另外token每一小時過期一次,後臺靜默刷新重新獲取token(在本項目中即登錄重新獲取token)

1. 攔截器,攔截網絡請求中的header:

ResponseInterceptor.class
class ResponseInterceptor implements Interceptor {

    private String cookie = "Set-Cookie";

    @Override
    public Response intercept(Chain chain) throws IOException {

        Request request = chain.request();
        Response response = chain.proceed(request);
        String cookie = response.header("Set-Cookie");
        if (StringUtils.isEmpty(cookie)) {
            return response;
        } else {
            Log.e("header", cookie);// header: JSESSIONID=2b62e33f-c72f-43f9-b963-ac098205d3c7; Path=/webroot; HttpOnly
            // 取出JSESSIONID=2b62e33f-c72f-43f9-b963-ac098205d3c7
            String newCookie;
            if (cookie.contains(";")) {
                newCookie = cookie.substring(0, cookie.indexOf(";"));
            } else {
                newCookie = cookie;
            }
            SharedCacheUtils.getInstance(App.instance.getApplicationContext()).setTOKEN(newCookie);
            Log.e("cookie", SharedCacheUtils.getInstance(App.instance.getApplicationContext()).getToken());
        }

        return response;
    }
}

2.打印log的攔截器:

private static Interceptor getLogInterceptor() {
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
            @Override
            public void log(String message) {
                //打印retrofit日誌
                Log.e("RetrofitLog", "retrofitBack = " + message);
            }
        });
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        return interceptor;
    }

 

3.

ServiceGenerator.class
public class ServiceGenerator {
    public static <S> S createService(Class<S> serviceClass, String baseUrl, Interceptor... interceptors) {

        OkHttpClient.Builder builder = new OkHttpClient.Builder().connectTimeout(60, TimeUnit.SECONDS)
                .readTimeout(60, TimeUnit.SECONDS)
                .writeTimeout(60, TimeUnit.SECONDS)
                .addInterceptor(getLogInterceptor())// 打印日誌
                .addInterceptor(new ResponseInterceptor())
                .addInterceptor(chain -> {
                    Request request = chain.request()
                            .newBuilder()
                            .addHeader("cookie", SharedCacheUtils.getInstance(App.instance.getApplicationContext()).getToken())
                            .addHeader("x-requested-with", "app")
                            .build();

                    return chain.proceed(request);
                });

        if (interceptors != null) {
            for (Interceptor interceptor : interceptors) {
                builder.addInterceptor(interceptor);
            }
        }

        OkHttpClient okHttpClient = builder.build();

//        Gson gson = new GsonBuilder().setLenient().create();

//        Retrofit retrofit = new Retrofit.Builder()
//                .baseUrl(baseUrl)
//                .client(okHttpClient)
//                .addConverterFactory(GsonConverterFactory.create())
////                .addConverterFactory(LenientGsonConverterFactory.create(gson))
//                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
//                .build();
//
//        return retrofit.create(serviceClass);

        S retrofit = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .client(okHttpClient)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build()
                .create(serviceClass);

        return (S) Proxy.newProxyInstance(serviceClass.getClassLoader(), new Class<?>[]{serviceClass}, new ProxyHandler(retrofit));
    }
}

 

4.

ProxyHandler代理攔截器:
public class ProxyHandler implements InvocationHandler {

    private final static String TOKEN = "token";

    private final static int REFRESH_TOKEN_VALID_TIME = 1000 * 60 * 60;// 單位毫秒,1小時過期一次
    private static long tokenChangedTime = 0;
    private Throwable mRefreshTokenError = null;
    private boolean mIsTokenNeedRefresh;

    private Object mProxyObject;
//    private IGlobalManager mGlobalManager;

    public ProxyHandler(Object proxyObject) {
        mProxyObject = proxyObject;
//        mGlobalManager = globalManager;
    }

    /**
     * Refresh the token when the current token is invalid.
     *
     * @return Observable
     */
    private Observable<?> refreshTokenWhenTokenInvalid() {
        synchronized (ProxyHandler.class) {
            // Have refreshed the token successfully in the valid time.
            if (new Date().getTime() - tokenChangedTime < REFRESH_TOKEN_VALID_TIME) {
                mIsTokenNeedRefresh = true;
                return Observable.just(true);
            } else {
                // 獲取用戶名與密碼,重新登錄
                UserHelper userHelper = new UserHelper();
                String username = "", psword = "";
                if (userHelper.getLastLoggedUser() != null) {
                    username = (userHelper.getLastLoggedUser().getUsername());
                    psword = SharedCacheUtils.getInstance(App.instance.getApplicationContext()).getPassword();
                }
                Network.checkNetwork(App.instance.getApplicationContext(), Network.getApisNews().login(username, psword, true))
                        .subscribe(new Observer<BaseEntityN>() {
                            @Override
                            public void onSubscribe(Disposable d) {

                            }

                            @Override
                            public void onNext(BaseEntityN token) {
                                // 會保存起來把cookie
//                                SharedCacheUtils.getInstance(App.instance.getApplicationContext()).setTOKEN(token);
                                Log.e("token", "mIsTokenNeedRefresh= " + mIsTokenNeedRefresh);
                                mIsTokenNeedRefresh = true;
                                tokenChangedTime = new Date().getTime();
                            }

                            @Override
                            public void onError(Throwable throwable) {
                                mRefreshTokenError = throwable;
                            }

                            @Override
                            public void onComplete() {

                            }
                        });
                if (mRefreshTokenError != null) {
                    Observable<Object> error = Observable.error(mRefreshTokenError);
                    mRefreshTokenError = null;
                    return error;
                } else {
                    return Observable.just(true);
                }
            }
        }
    }

    /**
     * Update the token of the args in the method.
     * <p>
     * PS: 因爲這裏使用的是 GET 請求,所以這裏就需要對 Query 的參數名稱爲 token 的方法。
     * 若是 POST 請求,或者使用 Body ,自行替換。因爲 參數數組已經知道,進行遍歷找到相應的值,進行替換即可(更新爲新的 token 值)。
     */
    @SuppressWarnings("unchecked")
    private void updateMethodToken(Method method, Object[] args) {
        String token = SharedCacheUtils.getInstance(App.instance.getApplicationContext()).getToken();
        if (mIsTokenNeedRefresh && !TextUtils.isEmpty(token)) {
            Annotation[][] annotationsArray = method.getParameterAnnotations();
            Annotation[] annotations;
            if (annotationsArray != null && annotationsArray.length > 0) {
                for (int i = 0; i < annotationsArray.length; i++) {
                    annotations = annotationsArray[i];
                    for (Annotation annotation : annotations) {
                        if (annotation instanceof FieldMap || annotation instanceof QueryMap) {
                            if (args[i] instanceof Map)
                                ((Map<String, Object>) args[i]).put(TOKEN, token);
                        } else if (annotation instanceof Query) {
                            if (TOKEN.equals(((Query) annotation).value()))
                                args[i] = token;
                        } else if (annotation instanceof Field) {
                            if (TOKEN.equals(((Field) annotation).value()))
                                args[i] = token;
                        } else if (annotation instanceof Part) {
                            if (TOKEN.equals(((Part) annotation).value())) {
                                RequestBody tokenBody = RequestBody.create(MediaType.parse("multipart/form-data"), token);
                                args[i] = tokenBody;
                            }
                        } else if (annotation instanceof Body) {
//                            if (args[i] instanceof PostRequestBody) {
//                                PostRequestBody requestData = (PostRequestBody) args[i];
//                                requestData.setToken(token);
//                                args[i] = requestData;

//                            }
                        }
                    }
                }
            }
            mIsTokenNeedRefresh = false;
        }
    }

    @Override
    public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {
        return Observable.just(true).flatMap(new Function<Object, ObservableSource<?>>() {
            @Override
            public ObservableSource<?> apply(Object o) throws Exception {
                try {
                    try {
                        if (mIsTokenNeedRefresh) {
                            updateMethodToken(method, args);
                        }
                        return (Observable<?>) method.invoke(mProxyObject, args);
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                return null;
            }
        }).retryWhen(new Function<Observable<Throwable>, ObservableSource<?>>() {
            @Override
            public ObservableSource<?> apply(Observable<Throwable> observable) throws Exception {
                return observable.flatMap(new Function<Throwable, ObservableSource<?>>() {
                    @Override
                    public ObservableSource<?> apply(Throwable throwable) throws Exception {
                        if (throwable instanceof TokenInvalidException) {
                            return refreshTokenWhenTokenInvalid();
                        }
//                        else if (throwable instanceof TokenNotExistException) {
//                            // Token 不存在,執行退出登錄的操作。(爲了防止多個請求,都出現 Token 不存在的問題,
//                            // 這裏需要取消當前所有的網絡請求)
//                            return Observable.error(throwable);
//                        }
                        return Observable.error(throwable);
                    }
                });
            }
        });
    }
}

5.返回的數據體,是不確定的,除了msg與code固定,其餘沒有固定的數據格式。所以用以下的方式。

如果是有固定的格式,固定都是msg,code, result(data)如下;

final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, Object> {

    private final TypeAdapter<T> adapter;

    GsonResponseBodyConverter(TypeAdapter<T> adapter) {
        this.adapter = adapter;
    }

    @Override
    public Object convert(ResponseBody value) throws IOException {
        try {
            BaseEntity response = (BaseEntity) adapter.fromJson(value.charStream());

            if (response == null) {
                return Observable.error(new Throwable("獲取內容失敗"));
            } else {
                if (response.isSuccess()) {
                    if (response.getValue() == null) {
                        if (response.getId() != null) {
                            return response.getId();
                        } else {
                            return null;
                        }
                    }
                    return response.getValue();
                } else if (response.isTokenRefreshSuccess()) {
                    return response.getToken();
                } else if (response.isTokenInvalid()) {
                    // token無效時處理
                    throw new TokenInvalidException();
                } else if (response.isCheckVersion()) {
                    // 是否是版本升級
                    return new CheckVersion(response.getVer(), response.getUrl());
                } else if (response.getData() != null) {
                    return response.getData();
                } else {
                    return new Throwable(response.getErrMsg());
                }
            }
        } finally {
            value.close();
        }
    }
}
GsonResponseBodyConverter.class
final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, Object> {
    private final Gson gson;
    private final TypeAdapter<T> adapter;

    GsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) {
        this.gson = gson;
        this.adapter = adapter;
    }

    @Override
    public Object convert(ResponseBody value) throws IOException {
        // 因爲你只能對ResponseBody讀取一次 , 如果你調用了response.body().string()兩次或者response.body().charStream()兩次就會出現這個異常,
        // 先調用string()再調用charStream()也不可以.
        // 所以通常的做法是讀取一次之後就保存起來,下次就不從ResponseBody裏讀取.

        String response = value.string();
        BaseEntityN baseEntityN = gson.fromJson(response, BaseEntityN.class);// BaseEntity response = (BaseEntity) adapter.fromJson(value.charStream()); 沒完全對上是不行的。
        if (baseEntityN.isTokenInvalid()) {
            value.close();
            throw new TokenInvalidException();
        }

        MediaType contentType = value.contentType();
        Charset charset = contentType != null ? contentType.charset(UTF_8) : UTF_8;
        InputStream inputStream = new ByteArrayInputStream(response.getBytes());
        Reader reader = new InputStreamReader(inputStream, charset);
        JsonReader jsonReader = gson.newJsonReader(reader);

        try {
            return adapter.read(jsonReader);
        } finally {
            value.close();
        }
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章