特點:
登錄後,從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();
}
}
}