网络

android 动态刷新token

2020-01-14  本文已影响0人  程序员阿兵

项目中验证用户会采用实时变更token的方式,一个refreshToken 五分钟过期一次,一个accessToken 一周失效。refreshToken过期需要刷新替换,accessToken需要过期退出登录。开发中会遇到场景:当前refreshToken已经过期,存在异步几个接口同时持有过期的refreshToken请求。这样的结果可想而知!

项目使用的是retrofit,结合retrofit 的动态代理设计模式,可以引以为用:

    /**
     * 接口service
     *
     * @param service 具体业务service
     * @param <T>     泛型
     * @return service 实例
     */
    public static <T> T getService(Class<T> service) {
        return createProxy(service);
    }

    private static <S> S createProxy(Class<S> serviceClass) {
        S s = getInstance().retrofit.create(serviceClass);
        return (S) Proxy.newProxyInstance(serviceClass.getClassLoader(), new Class<?>[]{serviceClass}, new ProxyHandler(s));
    }

下面是ProxyHandler 类,返回请求对象观察者:

/**
 * @author 桂雁彬
 * @date 2019-12-18.
 * GitHub:
 * description:
 */
public class ProxyHandler implements java.lang.reflect.InvocationHandler {
    private final Object target;
    private boolean mIsTokenNeedRefresh = true;
    private final static String TOKEN = "token";

    ProxyHandler(@NonNull final Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
        Object r = method.invoke(target, args);
        if (!(r instanceof Observable)) {
            return r;
        }
        return Observable.just(true)
                .flatMap((Function<Object, ObservableSource<?>>) o -> (Observable<?>) method.invoke(target, args)).retryWhen(new RetryWithDelay(2, 1000));
    }
}

由invoke 的返回可看具体操作类RetryWithDelay

public class RetryWithDelay implements Function<Observable<? extends Throwable>, Observable<?>> {
    private final int maxRetries;
    private final int retryDelayMillis;
    private long currentRefreshTime;
    private static final long REFRESH_TOKEN_TIME = 200;
    private boolean oneTimeRefresh;
    public RetryWithDelay(int maxRetries, int retryDelayMillis) {
        this.maxRetries = maxRetries;
        this.retryDelayMillis = retryDelayMillis;
    }
    @Override
    public Observable<?> apply(Observable<? extends Throwable> observable) {
        return observable.flatMap(new Function<Throwable, ObservableSource<?>>() {
            @Override
            public ObservableSource<?> apply(Throwable throwable) throws Exception {
                LogUtils.d("TtSy", "throwable:" + throwable);
                LogUtils.d("==refreshTpoken", "response:" + "onUnAuth" + "  thread:" + Thread.currentThread());

                if (throwable instanceof ApiException) {
                    ApiException apiException = (ApiException) throwable;
                    if (apiException.getCode() == 20001) { //token 过期 直接退出登录
                        // apiCode = 16  游客token过期
                        if (!oneTimeRefresh){
                            EventBus.getDefault().post(new BaseLoginOutEvent());
                            oneTimeRefresh=true;
                        }
                    }else  if (apiException.getCode() == 20002) { //持续刷新token

                        return onUnAuth(throwable);
                    }
                } else if (throwable instanceof HttpException) {
                    HttpException httpException = (HttpException) throwable;
                    if (httpException.code() == 401) {
                        if (!oneTimeRefresh){
                            EventBus.getDefault().post(new BaseLoginOutEvent());
                            oneTimeRefresh=true;
                        }
                    }
                }
                return Observable.error(throwable);
            }
        });
    }
    private Observable<?> onUnAuth(Throwable throwable) {
        synchronized (ProxyHandler.class) {
            // 防止多次调用
            if (System.currentTimeMillis() - currentRefreshTime < REFRESH_TOKEN_TIME) {
                return Observable.just(true);
            }
            return Observable.intervalRange(1, maxRetries, 0, retryDelayMillis, TimeUnit.MILLISECONDS)
                    .flatMap(new Function<Long, ObservableSource<?>>() {
                        @Override
                        public ObservableSource<?> apply(Long aLong) throws Exception {
                            LogUtils.d("==refreshTpoken", "response:" + "onUnAuth" + "  thread:" + Thread.currentThread());

                            if (aLong == maxRetries) {
                                return Observable.error(new ApiException("未知错误", -1));
                            }
                            return refreshToken();
                        }
                    });
        }
    }
    private Observable<?> refreshToken() {
        synchronized (ProxyHandler.class) {
            Map<String, Object> map = new HashMap<>(2);
            return RetrofitHelp.getService(RefreshTokenApiService.class)
                    .refreshToken(map)
                    .flatMap((Function<ResultEntity<RefreshToken>, ObservableSource<?>>) refreshTokenResultEntity -> {
                        LogUtils.d("==refreshTpoken", "response:" + refreshTokenResultEntity + "  thread:" + Thread.currentThread());
                        RefreshToken data = refreshTokenResultEntity.getData();
                        if (data != null && !TextUtils.isEmpty(data.getToken())) {
                            putToken(data.getToken());
                            currentRefreshTime = System.currentTimeMillis();
                            return Observable.just(true);
                        }
                        return Observable.error(new ApiException("未知错误", -1));
                    });
        }
    }

    /**
     * 保存本地token
     *
     * @param token
     */
    public void putToken(String token) {
        PSP.getInstance().remove(FinalKey.USER_TOKEN);
        PSP.getInstance().put(FinalKey.USER_TOKEN, token);
    }

}

上面的代码主要的思路是对应服务协议token失效会返回对应的code,当前 code 20002时去同步刷新token并返回最终的Observable,这样其他请求并不会同时异步持有过期token,会在本次刷新完后持有最新token刷新。

注:上面的

   if (throwable instanceof ApiException) {
                    ApiException apiException = (ApiException) throwable;
                    if (apiException.getCode() == 20001) { //token 过期 直接退出登录
                        // apiCode = 16  游客token过期
                        if (!oneTimeRefresh){
                            EventBus.getDefault().post(new BaseLoginOutEvent());
                            oneTimeRefresh=true;
                        }
                    }else  if (apiException.getCode() == 20002) { //持续刷新token

                        return onUnAuth(throwable);
                    }

是在网络请求根据服务端返回对应的code,如果不是请求成功码会抛出异常,由rxJava 处理最终拿到回调throwable。
可以在自定义Converter 中实现:

public class CustomGsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
    private final Gson gson;
    private final TypeAdapter<T> adapter;

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

    @Override
    public T convert(@NonNull ResponseBody value) throws IOException {
        String string = value.string();

        LogUtils.d("==temp==",string);
        ResultEntity baseNewEntity = gson.fromJson(string, ResultEntity.class);

        if (baseNewEntity == null) {
            value.close();
            throw new ApiException("数据错误", -1);
        }

        if (!baseNewEntity.isSuccess()) {
            value.close();
            throw new ApiException(baseNewEntity.getMsg(), baseNewEntity.getCode());
        }

        MediaType contentType = value.contentType();
        Charset charset = contentType != null ? contentType.charset(UTF_8) : UTF_8;
        InputStream inputStream = new ByteArrayInputStream(string.getBytes());
        Reader reader = new InputStreamReader(inputStream, charset);
        JsonReader jsonReader = gson.newJsonReader(reader);
        try {
            return adapter.read(jsonReader);
        } finally {
            value.close();
        }
    }

    private static final Charset UTF_8 = Charset.forName("UTF-8");
}

上一篇 下一篇

猜你喜欢

热点阅读