特点:
登录后,从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();
}
}
}