|
|
@@ -37,8 +37,14 @@ class Login extends BaseService
|
|
|
// 用于生成token的自定义盐
|
|
|
const TOKEN_SALT = 'user_salt';
|
|
|
|
|
|
+ // 同一手机号允许的连续登录失败次数
|
|
|
+ const LOGIN_FAIL_LIMIT = 5;
|
|
|
+
|
|
|
+ // 登录失败锁定时间 (秒)
|
|
|
+ const LOGIN_FAIL_LOCK_TIME = 600;
|
|
|
+
|
|
|
/**
|
|
|
- * 执行用户登录
|
|
|
+ * 执行用户登录 (手机号 + 密码)
|
|
|
* @param array $data
|
|
|
* @return bool
|
|
|
* @throws BaseException
|
|
|
@@ -51,8 +57,10 @@ class Login extends BaseService
|
|
|
{
|
|
|
// 数据验证
|
|
|
$this->validate($data);
|
|
|
- // 自动登录注册
|
|
|
- $this->register($data);
|
|
|
+ // 兜底: 第三方用户信息 (非必填)
|
|
|
+ $data = \array_merge(['isParty' => false, 'partyData' => []], $data);
|
|
|
+ // 手机号 + 密码登录 (未注册的手机号自动注册)
|
|
|
+ $this->loginByPassword($data['mobile'], $data['password'], $data['isParty'], $data['partyData']);
|
|
|
// 保存第三方用户信息
|
|
|
$this->createUserOauth($this->getUserId(), $data['isParty'], $data['partyData']);
|
|
|
// 记录登录态
|
|
|
@@ -192,7 +200,87 @@ class Login extends BaseService
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 自动登录注册
|
|
|
+ * 手机号 + 密码登录
|
|
|
+ * 1. 用户不存在: 自动注册并设置密码 (与页面文案“未注册的手机号登录后将自动注册”一致)
|
|
|
+ * 2. 用户存在但未设置密码: 本次输入的密码作为初始密码 (兼容历史短信登录的老用户)
|
|
|
+ * 3. 用户已设置密码: 校验密码, 失败累计次数并锁定
|
|
|
+ * @param string $mobile 手机号
|
|
|
+ * @param string $password 登录密码(明文)
|
|
|
+ * @param bool $isParty 是否存在第三方用户信息
|
|
|
+ * @param array $partyData 第三方用户数据
|
|
|
+ * @return void
|
|
|
+ * @throws BaseException
|
|
|
+ * @throws \think\Exception
|
|
|
+ * @throws \think\db\exception\DataNotFoundException
|
|
|
+ * @throws \think\db\exception\DbException
|
|
|
+ * @throws \think\db\exception\ModelNotFoundException
|
|
|
+ */
|
|
|
+ private function loginByPassword(string $mobile, string $password, bool $isParty, array $partyData = []): void
|
|
|
+ {
|
|
|
+ // 查询用户是否已存在
|
|
|
+ $userInfo = UserModel::detail(['mobile' => $mobile]);
|
|
|
+ // 用户不存在: 注册新用户并设置密码
|
|
|
+ if (empty($userInfo)) {
|
|
|
+ $this->createUser($mobile, $isParty, $partyData, $password);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // 用户已存在但尚未设置密码: 本次输入的密码作为初始密码
|
|
|
+ if (!$userInfo->hasPassword()) {
|
|
|
+ $this->updateUser($userInfo, $isParty, $partyData, $password);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // 是否已被锁定
|
|
|
+ $this->checkLoginFailTimes($mobile);
|
|
|
+ // 校验密码
|
|
|
+ if (!$userInfo->verifyPassword($password)) {
|
|
|
+ $this->recordLoginFail($mobile);
|
|
|
+ throwError('手机号或密码不正确');
|
|
|
+ }
|
|
|
+ // 登录成功, 清空失败记录
|
|
|
+ Cache::delete($this->getLoginFailCacheKey($mobile));
|
|
|
+ // 更新用户登录信息
|
|
|
+ $this->updateUser($userInfo, $isParty, $partyData);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 登录失败次数缓存的key
|
|
|
+ * @param string $mobile
|
|
|
+ * @return string
|
|
|
+ */
|
|
|
+ private function getLoginFailCacheKey(string $mobile): string
|
|
|
+ {
|
|
|
+ return 'login_fail_' . md5("{$this->storeId}_{$mobile}");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查登录失败次数 (超出限制则锁定)
|
|
|
+ * @param string $mobile
|
|
|
+ * @return void
|
|
|
+ * @throws BaseException
|
|
|
+ */
|
|
|
+ private function checkLoginFailTimes(string $mobile): void
|
|
|
+ {
|
|
|
+ $times = (int)Cache::get($this->getLoginFailCacheKey($mobile), 0);
|
|
|
+ if ($times >= self::LOGIN_FAIL_LIMIT) {
|
|
|
+ $minutes = (int)(self::LOGIN_FAIL_LOCK_TIME / 60);
|
|
|
+ \throwError("密码错误次数过多, 请{$minutes}分钟后再试");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 记录一次登录失败
|
|
|
+ * @param string $mobile
|
|
|
+ * @return void
|
|
|
+ */
|
|
|
+ private function recordLoginFail(string $mobile): void
|
|
|
+ {
|
|
|
+ $key = $this->getLoginFailCacheKey($mobile);
|
|
|
+ $times = (int)Cache::get($key, 0) + 1;
|
|
|
+ Cache::set($key, $times, self::LOGIN_FAIL_LOCK_TIME);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 自动登录注册 (第三方/授权手机号登录, 无需密码)
|
|
|
* @param array $data
|
|
|
* @throws BaseException
|
|
|
* @throws \think\Exception
|
|
|
@@ -218,13 +306,14 @@ class Login extends BaseService
|
|
|
* @param string $mobile 手机号
|
|
|
* @param bool $isParty 是否存在第三方用户信息
|
|
|
* @param array $partyData 用户信息(第三方)
|
|
|
+ * @param string $password 登录密码(明文, 为空则不设置)
|
|
|
* @return void
|
|
|
* @throws \think\Exception
|
|
|
* @throws \think\db\exception\DataNotFoundException
|
|
|
* @throws \think\db\exception\DbException
|
|
|
* @throws \think\db\exception\ModelNotFoundException
|
|
|
*/
|
|
|
- private function createUser(string $mobile, bool $isParty, array $partyData = []): void
|
|
|
+ private function createUser(string $mobile, bool $isParty, array $partyData = [], string $password = ''): void
|
|
|
{
|
|
|
// 用户信息
|
|
|
$data = [
|
|
|
@@ -234,6 +323,10 @@ class Login extends BaseService
|
|
|
'last_login_time' => \time(),
|
|
|
'store_id' => $this->storeId
|
|
|
];
|
|
|
+ // 登录密码 (模型修改器会自动使用 password_hash 加密)
|
|
|
+ if ($password !== '') {
|
|
|
+ $data['password'] = $password;
|
|
|
+ }
|
|
|
// 写入用户信息(第三方)
|
|
|
if ($isParty === true && !empty($partyData)) {
|
|
|
$partyUserInfo = PartyService::partyUserInfo($partyData, true);
|
|
|
@@ -259,14 +352,19 @@ class Login extends BaseService
|
|
|
* @param UserModel $userInfo
|
|
|
* @param bool $isParty 是否存在第三方用户信息
|
|
|
* @param array $partyData 用户信息(第三方)
|
|
|
+ * @param string $password 登录密码(明文, 为空则不更新)
|
|
|
*/
|
|
|
- private function updateUser(UserModel $userInfo, bool $isParty, array $partyData = []): void
|
|
|
+ private function updateUser(UserModel $userInfo, bool $isParty, array $partyData = [], string $password = ''): void
|
|
|
{
|
|
|
// 用户信息
|
|
|
$data = [
|
|
|
'last_login_time' => \time(),
|
|
|
'store_id' => $this->storeId
|
|
|
];
|
|
|
+ // 设置/更新登录密码 (模型修改器会自动使用 password_hash 加密)
|
|
|
+ if ($password !== '') {
|
|
|
+ $data['password'] = $password;
|
|
|
+ }
|
|
|
// 写入用户信息(第三方)
|
|
|
// 如果不需要每次登录都更新微信用户头像昵称, 下面几行代码可以屏蔽掉
|
|
|
// if ($isParty === true && !empty($partyData)) {
|
|
|
@@ -310,17 +408,11 @@ class Login extends BaseService
|
|
|
*/
|
|
|
private function validate(array $data): void
|
|
|
{
|
|
|
- // 数据验证
|
|
|
+ // 数据验证 (手机号 + 登录密码)
|
|
|
$validate = new ValidateLogin;
|
|
|
if (!$validate->check($data)) {
|
|
|
throwError($validate->getError());
|
|
|
}
|
|
|
- // 验证短信验证码是否匹配
|
|
|
- // try {
|
|
|
- // CaptchaApi::checkSms($data['smsCode'], $data['mobile']);
|
|
|
- //} catch (\Exception $e) {
|
|
|
- // throwError($e->getMessage() ?: '短信验证码不正确');
|
|
|
- // }
|
|
|
}
|
|
|
|
|
|
/**
|