首先封装了dio,可以去看我之前的文章,里面有添加拦截器,这是关键。
token拦截器
import 'package:dio/dio.dart';
import 'package:cserp/datautil.dart';
import 'package:cserp/login.dart';
//QueuedInterceptors是队列,不会造成并发进入拦截器
class TokenInterceptor extends QueuedInterceptorsWrapper {
@override
void onRequest(
RequestOptions options, RequestInterceptorHandler handler) async {
// print('REQUEST[${options.method}] => PATH: ${options.path}');
if (!options.headers.containsKey('Authorization')) {
String? token = await getToken();
if (token == null) {
//走到这里,就代表这handler里没有Authorization这个字段,并且本地也没有token,所以弹出登录页面,我们会在showLoginPage里 handler.next(options);
showLoginPage(options: options, handler: handler);
return;
}
options.headers['Authorization'] = await getToken();
return handler.next(options);
}
options.headers['Authorization'] = await getToken();
return handler.next(options);
}
// @override
// void onResponse(Response response, ResponseInterceptorHandler handler) {
// print(
// 'RESPONSE[${response.statusCode}] => PATH: ${response.requestOptions.path}');
// super.onResponse(response, handler);
// }
@override
Future onError(DioException err, ErrorInterceptorHandler handler) async {
// print(
// 'ERROR[${err.response?.statusCode}] => PATH: ${err.requestOptions.path}');
if (err.response?.statusCode == 401) {
//这里是发出的请求401了,需要登录,所以跳出登录页面,我们也在showLoginPage里return handler.next(err);
showLoginPage(options: err.requestOptions, handler: handler);
return;
}
return handler.next(err);
}
}
showLoginPage():
import 'package:flutter/cupertino.dart';
import 'package:cserp/global.dart';
import 'package:flutter/material.dart';
import 'package:cserp/http.dart';
import 'package:dio/dio.dart';
import 'package:cserp/datautil.dart';
//重要的是这个方法,handler用的泛型,因为可能是RequestInterceptorHandler或者ErrorInterceptorHandler
showLoginPage<T>({RequestOptions? options, T? handler}) {
late TextEditingController usernameController = TextEditingController();
late TextEditingController passwordController = TextEditingController();
Future login() async {
//发起登录请求,
await loginReq(usernameController.text, passwordController.text,
success: (data) async {
if (data.data!.token != null) {
await setToken(data.data!.token.toString());
BuildContext context = Global.navigatorKey.currentState!.context;
if (context.mounted) {
Navigator.of(context).pop();
}
}
//这里是重中之重,
if (handler != null && options != null) {
if (handler is RequestInterceptorHandler) {
options.headers['Authorization'] = await getToken();
//到这里代表着,登录已经完毕,也已经获取到了token,然后把token塞到headers里,继续下一个拦截器,直到发起请求
return handler.next(options);
} else if (handler is ErrorInterceptorHandler) {
//发起玩请求了返回来需要登录,然后现在已经登录完毕了,再把token塞到header里
options.headers['Authorization'] = await getToken();
final originResult = await dio.fetch(options);
//最后再重新发起这个请求
return handler.resolve(originResult);
}
}
});
}
//这些不重要,每个人的登录界面都不一样
showCupertinoModalPopup<void>(
context: Global.navigatorKey.currentState!.context,
builder: (BuildContext context) => CupertinoPopupSurface(
child: Container(
color: CupertinoColors.white,
width: double.infinity,
height: MediaQuery.of(context).size.height - 50,
child: Padding(
padding: const EdgeInsets.only(left: 10, right: 10),
child: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
CupertinoButton(
padding: EdgeInsets.all(0),
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('取消', textAlign: TextAlign.left),
),
CupertinoButton(
padding: EdgeInsets.all(0),
onPressed: () => login(),
child: const Text('登录', textAlign: TextAlign.right),
),
],
),
Text("登录"),
Text("重生ERP"),
Column(
children: <Widget>[
Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(10)),
color: CupertinoColors.lightBackgroundGray,
),
child: CupertinoTextField(
controller: usernameController,
prefix: Padding(
padding: const EdgeInsets.only(left: 10, right: 10),
child: Text("账号"),
),
keyboardType: TextInputType.text,
textInputAction: TextInputAction.next,
decoration: BoxDecoration(
color: CupertinoColors.lightBackgroundGray,
border: Border(),
),
),
),
Divider(
height: 0,
color: CupertinoColors.inactiveGray,
),
Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10)),
color: CupertinoColors.lightBackgroundGray,
),
child: CupertinoTextField(
controller: passwordController,
prefix: Padding(
padding: const EdgeInsets.only(left: 10, right: 10),
child: Text("密码"),
),
keyboardType: TextInputType.text,
obscureText: true,
textInputAction: TextInputAction.done,
onEditingComplete: () => login(),
decoration: BoxDecoration(
color: CupertinoColors.lightBackgroundGray,
border: Border(),
),
),
),
],
),
],
),
),
// child: CupertinoPageScaffold(
// navigationBar: CupertinoNavigationBar(
// // Try removing opacity to observe the lack of a blur effect and of sliding content.
// // backgroundColor: CupertinoColors.systemGrey.withOpacity(0.5),
// // middle: const Text('CupertinoNavigationBar Sample'),
// leading: Text("取消"),
// // This title is visible in both collapsed and expanded states.
// // When the "middle" parameter is omitted, the widget provided
// // in the "largeTitle" parameter is used instead in the collapsed state.
// trailing: Text("登录"),
// backgroundColor: CupertinoColors.white,
// ),
// // A ScrollView that creates custom scroll effects using slivers.
// child: Container()),
)));
}
我的dio封装代码
import 'package:dio/dio.dart';
import 'package:cserp/token_interceptor.dart';
import 'package:cserp/err_interceptor.dart';
import 'package:cserp/result/login_result.dart';
final BaseOptions _baseOption = BaseOptions(
connectTimeout: Duration(seconds: 3),
baseUrl: "http://192.168.50.13/erp/",
contentType: "application/json; charset=utf-8");
final dio = Dio(_baseOption);
final lgoinDio = Dio(_baseOption);
dioInit() {
dio.interceptors
..add(TokenInterceptor())
..add(ErrInterceptor());
lgoinDio.interceptors.add(ErrInterceptor());
}
Future<Response> post(String url, {String? data, Function? success}) async {
Response response =
await dio.request(url, data: data, options: Options(method: "POST"));
if (success != null) {
success();
}
return response;
}
Future<Response?> loginReq(String username, String password,
{Function(LoginResult)? success}) async {
Response response = await lgoinDio
.post("/user/login", data: {"username": username, "password": password});
LoginResult lr = LoginResult.fromJson(response.data);
if (success != null) {
success(lr);
}
return response;
}
ErrInterceptor代码
import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
import 'package:cserp/global.dart';
class ErrInterceptor extends QueuedInterceptor {
@override
Future onError(DioException err, ErrorInterceptorHandler handler) async {
if (err.response?.statusCode == 500) {
showCupertinoModalPopup<void>(
context: Global.navigatorKey.currentState!.context,
builder: (BuildContext context) => CupertinoAlertDialog(
title: Text("提示"),
content: Text(err.response!.data['msg']),
actions: <CupertinoDialogAction>[
CupertinoDialogAction(
/// This parameter indicates this action is the default,
/// and turns the action's text to bold text.
isDefaultAction: true,
onPressed: () {
Navigator.pop(context);
return handler.reject(
DioException(requestOptions: err.requestOptions),
);
},
child: const Text('好'),
),
],
),
);
}
return handler.next(err);
}
}