// +---------------------------------------------------------------------- declare (strict_types=1); namespace app\api\service\passport; use app\api\model\User as UserModel; use app\api\service\User as UserService; use app\api\service\user\Oauth as OauthService; use app\api\service\user\Avatar as AvatarService; use app\api\validate\passport\Login as ValidateLogin; use app\common\exception\BaseException; use app\common\service\BaseService; use edward\captcha\facade\CaptchaApi; use think\cache\driver\Redis; use think\facade\Cache; /** * 服务类:用户登录 * Class Login * @package app\api\service\passport */ class Login extends BaseService { // 用户信息 (登录成功后才记录) private $userInfo; // 用于生成token的自定义盐 const TOKEN_SALT = 'user_salt'; const OPEN_ID_KEY = 'ysc_xcx_openid:'; /** * 执行用户登录 * @param array $data * @return bool * @throws BaseException * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\DbException * @throws \think\db\exception\ModelNotFoundException */ public function login(array $data) { // 数据验证 if (!$this->validate($data)) { return false; } // 自动登录注册 $this->register($data); // 保存oauth信息 $this->oauth($data); // 记录登录态 return $this->session(); } /** * 快捷登录:微信小程序用户 * @param array $data * @return bool * @throws BaseException * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\DbException * @throws \think\db\exception\ModelNotFoundException */ public function mpWxLogin(array $data) { try { // 根据code换取openid $wxSession = OauthService::wxCode2Session($data['code']); } catch (BaseException $e) { // showError参数表示让前端显示错误 throwError($e->getMessage(), null, ['showError' => true]); return false; } // 缓存微信用户信息 $rds = new Redis(config('cache.stores.redis')); $key = self::OPEN_ID_KEY.$wxSession['openid']; $rds->set($key,$wxSession); // 判断openid是否存在 $userId = OauthService::getUserIdByOauthId($wxSession['openid'], 'MP-WEIXIN'); // 获取用户信息 $userInfo = !empty($userId) ? UserModel::detail($userId) : null; if (empty($userId) || empty($userInfo)) { throwError('第三方用户不存在', 200, ['code' => $data['code'],'token'=>'','openid'=>$wxSession['openid']]); return false; } // 更新用户登录信息 $this->updateUser($userInfo); // 记录登录态 return $this->session(); } /** * 保存oauth信息 * @param array $data * @return bool * @throws BaseException * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\DbException * @throws \think\db\exception\ModelNotFoundException */ private function oauth(array $data) { if ($data['isParty']) { $Oauth = new OauthService; return $Oauth->party((int)$this->userInfo['user_id'], $data); } return true; } /** * 当前登录的用户信息 * @return array */ public function getUserInfo() { return $this->userInfo; } /** * 自动登录注册 * @param array $data * @return bool */ private function register(array $data) { // 查询用户是否已存在 $userInfo = UserModel::detail(['mobile' => $data['mobile']]); if ($userInfo) { // 用户存在: 更新登录信息 return $this->updateUser($userInfo); } else { // 用户不存在: 新增用户 $data['mobile'] = (string)$data['mobile']; return $this->createUser($data['mobile'], $data['userId'], $data['openid']??'', (int)($data['actSource'] ?? 0), (int)($data['actSourceId'] ?? 0)); } } /** * 新增用户 * @param string $mobile 手机号 * @param int $userId 用户id * @param string $openid * @param int $actSource 活动来源 * @param int $actSourceId 活动来源id * @return bool */ private function createUser(string $mobile, int $userId = 0, string $openid = '', int $actSource = 0, int $actSourceId = 0) { // 用户信息 $data = [ 'open_id' => $openid, 'mobile' => $mobile, 'nick_name' => make_nickname($mobile), 'platform' => getPlatform(), 'last_login_time' => time(), 'store_id' => $this->storeId, 'act_source' => $actSource, 'act_source_id' => $actSourceId, ]; if ($userId) { $data['user_id'] = $userId; } // 写入用户信息(第三方) // if ($isParty === true && !empty($partyData)) { // $partyUserInfo = $this->partyUserInfo($partyData, true); // $data = array_merge($data, $partyUserInfo); // } // 新增用户记录 $model = new UserModel; $status = $model->save($data); // 记录用户信息 $this->userInfo = $model; return $status; } public function updateWxUserInfo(array $partyUserInfo, $userInfo) { $data = $this->partyUserInfo($partyUserInfo); // 更新用户记录 $status = $userInfo->save($data) !== false; // 记录用户信息 $this->userInfo = $userInfo; return $status; } /** * 第三方用户信息 * @param array $partyUserInfo 第三方用户信息 * @param bool $isGetAvatarUrl 是否下载头像 * @return array */ private function partyUserInfo(array $partyUserInfo, bool $isGetAvatarUrl = true) { if (!empty($partyUserInfo['nickName'])){ $data['nick_name'] = $partyUserInfo['nickName']; } $data['gender'] = $partyUserInfo['gender']??0; // $data = [ // 'nick_name' => $partyUserInfo['nickName'] ?? "用户", // 'gender' => $partyUserInfo['gender']??0 // ]; // 下载用户头像 if ($isGetAvatarUrl && !empty($partyUserInfo['avatarUrl'])) { $data['avatar_id'] = $this->partyAvatar($partyUserInfo['avatarUrl']); } return $data; } /** * 下载第三方头像并写入文件库 * @param string $avatarUrl * @return int */ private function partyAvatar(string $avatarUrl) { $Avatar = new AvatarService; $fileId = $Avatar->party($avatarUrl); return $fileId ? $fileId : 0; } /** * 更新用户登录信息 * @param UserModel $userInfo * @return bool */ private function updateUser(UserModel $userInfo) { // 用户信息 $data = [ 'last_login_time' => time(), 'store_id' => $this->storeId ]; // 写入用户信息(第三方) // if ($isParty === true && !empty($partyData)) { // $partyUserInfo = $this->partyUserInfo($partyData, !$userInfo['avatar_id']); // $data = array_merge($data, $partyUserInfo); // } // 更新用户记录 $status = $userInfo->save($data) !== false; // 记录用户信息 $this->userInfo = $userInfo; return $status; } /** * 记录登录态 * @return bool * @throws BaseException */ private function session() { empty($this->userInfo) && throwError('未找到用户信息'); // 登录的token $token = $this->getToken((int)$this->userInfo['user_id']); // 记录缓存, 30天 Cache::set($token, [ 'user' => $this->userInfo, 'store_id' => $this->storeId, 'is_login' => true, ], 86400 * 30); return true; } /** * 数据验证 * @param array $data * @return bool */ private function validate(array $data) { // 数据验证 $validate = new ValidateLogin; if (!$validate->check($data)) { $this->error = $validate->getError(); return false; } // 验证短信验证码是否匹配 // if (!CaptchaApi::checkSms($data['smsCode'], $data['mobile'])) { // $this->error = '短信验证码不正确'; // return false; // } return true; } /** * 获取登录的token * @param int $userId * @return string */ public function getToken(int $userId) { static $token = ''; if (empty($token)) { $token = $this->makeToken($userId); } return $token; } /** * 生成用户认证的token * @param int $userId * @return string */ public function makeToken(int $userId) { $storeId = $this->storeId; // 生成一个不会重复的随机字符串 $guid = get_guid_v4(); // 当前时间戳 (精确到毫秒) $timeStamp = microtime(true); // 自定义一个盐 $salt = self::TOKEN_SALT; return md5("{$storeId}_{$timeStamp}_{$userId}_{$guid}_{$salt}"); } }