登录流程
登陆机制的流程其实很简单,简单来说就是:
- 通过手机号获取验证码
- 输入验证码,根据手机号和验证码去登陆
- 登陆的时候设置用户的登录状态,下次打开以后免登录
1.验证码获取
TextField(
controller: _codeController
)
使用文本框小部件中的controller
的回调函数获取文本框中的内容
getCodeBuilder() {
_presenter.getCode(_phoneNumberController.text);
}
调用LoginPresent
类中的getCode
方法根据手机号去获取验证码,如果返回的是ture
,表示获取验证码成功,如果返回的是null
,表示获取验证码失败。
这里的Boast.show()
是Boast
类里面的一个静态方法,方法接收一个必选参数(显示信息)和一个可选参数(延迟时间,默认是一秒)。
getCode(String mobile) async {
try {
List result = await Future.wait([
_securityCodeDatasourceApi.getSecurityCodeByMobile(mobile.trim()),
]);
bool isSuccess = result[0];
if (isSuccess == null) {
Boast.show('验证码发送失败');
return;
}
Boast.show('验证码已发送');
} catch (e) {
Boast.show('验证码发送失败');
}
}
下面这段代码就是调用API类的getSecurityCodeByMobile
方法,调用接口,通过手机号去获取验证码。
_securityCodeDatasourceApi.getSecurityCodeByMobile(mobile.trim())
getSecurityCodeByMobile
方法的具体内容如下:
通过NetworkUtil
类的get
方法调用接口,获得数据并返回;
then里面的(dynamic res)
的res
就是get
方法得到的结果数据。
Future<bool> getSecurityCodeByMobile(String mobile) {
return _netUtil
.get('member', '/api/xxx/xxx?mobile=$mobile')
.then((dynamic res) {
if (res != null) {
return res['isSuccess'];
}
return null;
});
}
解释一下上面的get方法
Future<dynamic> get(String b, String url) {
if (b != null) {
//获取biz和url将url进行拼接,biz_domains是一个配置文件
url = b_domains[b] + url;
}
//getHeaders()方法完善请求头信息,然后
return getHeaders().then((headers) {
return http.get(url, headers: headers).then((response) {
//响应体内容
final String res = response.body;
//响应的状态
final int statusCode = response.statusCode;
//根据状态判断该抛出什么异常
if (statusCode < 200 || statusCode > 400 || json == null) {
throw new Exception("Error while fetching data");
}
//返回一个Map<String, dynamic>类型的数据
return json.decode(res);
});
});
}
上面代码中用到的getHeaders()
方法
Future<Map<String, String>> getHeaders() {
//AuthStateProvider类其实就是一恶搞用户登录状态监听的类,
return (new AuthStateProvider()).getLoginInfo().then((loginInfo) {
//定义响应头的Content-Type类型是json类型
Map<String, String> headers = {'Content-Type': 'application/json'};
//如果登陆信息不为空,响应头部添加token信息
if (loginInfo != null) {
headers['Authorization'] = 'Bearer ${loginInfo.token}';
}
return headers;
});
}
2.手机、验证码验证登陆
用户输入手机号和收到的验证码,点击登陆,触发点击事件,点击事件通过LoginPresent
类中的loginByMobile
方法去验证登陆
//点击事件的回调函数
void _submit() {
_presenter.loginByMobile(_phoneNumberController.text, _codeController.text);
}
//点击按钮
RaisedButton(
onPressed: _submit,
);
loginByMobile
函数解析:
loginByMobile(String mobile, String code) async {
AuthStateProvider provider = new AuthStateProvider();
try {
//通过API获取用户登录信息
LoginInfo loginInfo = await _securityCodeDatasourceApi.loginByMobile(
mobile.trim(), code.trim());
//如果token为空,登陆失败
if (loginInfo.token.isEmpty) {
BMToast.show('网络连接失败');
return;
}
//登陆成功以后设置登陆信息
await provider.setLoginInfo(loginInfo);
//调用api获取用户信息
User user = await api.getUserInfo();
//设置用户信息
await provider.setUserInfo(user);
} catch (e) {
BMToast.show('网络连接失败');
}
}
登陆的流程其实和验证码获取的流程差不多的,多出来的一部分就是如果用户登陆成功,那么就要记住用户已经登陆过的这个状态,下次打开app的时候就不用再去登陆了。
要做到这些,就需要刚才的两步:
//登陆成功以后设置登陆信息
await provider.setLoginInfo(loginInfo);
//调用api获取用户信息
User user = await api.getUserInfo();
//设置用户信息
await provider.setUserInfo(user);
这里的setLoginInfo
方法是AuthStateProvider类里的一个方法,用来设置登陆信息,这里的PRES_LOGIN_INFO_KEY
其实就是token
的前缀,loginInfo.toJson()
这里面其实就只有token
,最后 this.notify(AuthState.LOGGED_IN);
将用户的登录状态变成了“登陆状态”。
void setLoginInfo(LoginInfo loginInfo) async {
this.loginInfo = loginInfo;
await StorageUtil.set(PRES_LOGIN_INFO_KEY, loginInfo.toJson());
this.notify(AuthState.LOGGED_IN);
}
而StorageUtil.set()
方法则是存储用户登陆信息的一个类:
这个方法主要是根据data
的类型类分别决定要用prefs
里的哪个方法,而这个SharedPreferences
类暂时没有看懂
static set(String key, data) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
if (data is int) {
prefs.setInt(key, data);
} else if (data is double) {
prefs.setDouble(key, data);
} else if (data is String) {
prefs.setString(key, data);
} else if (data is List<String>) {
prefs.setStringList(key, data);
} else if (data is bool) {
prefs.setBool(key, data);
} else if (data is Map) {
prefs.setString(key, json.encode(data));
} else {
throw new Exception('cannt found data type');
}
}
最后要说的是
await provider.setUserInfo(user);
首先看provider.setUserInfo()方法
void setUserInfo(User userInfo) async {
this.userInfo = userInfo;
await StorageUtil.set(PRES_USER_INFO_KEY, userInfo.toJson());
}
这个方法做的事情有
- 为本类中的userInfo赋值
- 存储用户信息,以key:value的形式。
这里的StorageUtil虽然看不懂,但是可以根据await判断,这个数据应该是存储到了远程。同样登陆信息也是存储在了远程。
至此,登陆流程因该是走通了。