6
0
Fork 0
jiqu-library-server/app/Endpoint/Api/Http/Controllers/Auth/SocialiteAuthController.php

307 lines
9.7 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
namespace App\Endpoint\Api\Http\Controllers\Auth;
use App\Constants\Device;
use App\Endpoint\Api\Http\Controllers\Controller;
use App\Enums\SocialiteType;
use App\Exceptions\BizException;
use App\Helpers\PhoneNumber;
use App\Models\SmsCode;
use App\Models\SocialiteUser;
use App\Models\User;
use App\Rules\PhoneNumber as PhoneNumberRule;
use App\Services\SmsCodeService;
use EasyWeChat\Factory as EasyWeChatFactory;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Overtrue\Socialite\SocialiteManager;
use Throwable;
class SocialiteAuthController extends Controller
{
/**
* 三方code登录
*/
public function codeAuth($provider, Request $request)
{
$input = $request->validate([
'code' => ['bail', 'required', 'string'],
]);
$code = $input['code'];
//获取第三方用户信息
$socialite = $this->getSocialiteUserByCode($provider, $code);
//通过第三方用户信息登录已绑定账号
$token = null;
$socialiteUser = SocialiteUser::firstOrCreate([
'socialite_type' => $provider,
'socialite_id' => $socialite?->id,
]);
$user = $socialiteUser->user;
if ($user) {
$token = $this->loginUser($user, $request);
}
return response()->json([
'token' => $token?->plainTextToken,
]);
}
public function codeBindUser($provider, Request $request)
{
$type = $request->input('type', 'default');
$rules = [
'code' => ['bail', 'required', 'string'],
'inviter_code' => ['bail', 'nullable', 'string'],
];
switch ($type) {
case 'default'://手机号+密码
$rules = array_merge($rules, [
'phone' => ['bail', 'required', 'string'],
'password' => ['bail', 'required', 'string'],
]);
break;
case 'sms-code'://手机号+验证码
$rules = array_merge($rules, [
'phone' => ['bail', 'required', new PhoneNumberRule()],
'verify_code' => ['bail', 'required', 'string'],
]);
break;
case 'wechat-mini'://微信小程序解密手机号
$rules = array_merge($rules, [
'data' => ['bail', 'required', 'string'],
'iv' => ['bail', 'required', 'string'],
]);
break;
default://默认手机号+密码
$rules = array_merge($rules, [
'phone' => ['bail', 'required', 'string'],
'password' => ['bail', 'required', 'string'],
]);
break;
}
$input = $request->validate($rules);
$code = $input['code'];
//获取第三方用户信息
$socialite = $this->getSocialiteUserByCode($provider, $code);
//绑定用户并返回token
$token = $this->bindUser([
'socialite_type'=>$provider,
'socialite_id'=>$socialite?->id,
], $type ?? 'default', $request);
return response()->json([
'token' => $token?->plainTextToken,
]);
}
/**
* 第三方登录的解绑
*
* @param [type] $provider
* @param Request $request
*/
public function unbindUser($provider, Request $request)
{
if ($user = $request->user()) {
//解绑三方的关系
SocialiteUser::where([
'user_id' => $user->id,
'socialite_type' => $provider,
])->update([
'user_id' => null,
]);
}
return response()->noContent();
}
/**
* [目前支持:微信小程序]
*/
protected function getSocialiteUserByCode($provider, $code)
{
//获取第三方用户信息
$user = null;
$config = config('socialite', []);
$socialite = new SocialiteManager($config);
switch ($provider) {
case SocialiteType::WechatMiniProgram->value: //微信小程序
$user = $socialite->create(SocialiteType::WechatMiniProgram->value)->userFromCode($code);
break;
default:
throw new BizException(404);
}
return $user;
}
/**
* 第三方绑定用户
*
* @param [array] $socialite
* @param [Request] $request
*/
protected function bindUser(array $socialite, string $type, Request $request)
{
$token = null;
$socialiteUser = SocialiteUser::firstOrCreate($socialite);
$user = null;
$input = $request->input();
$phone = $input['phone'] ?? '';
switch ($type) {
case 'default'://手机号+密码
$user = User::where('phone', $phone)->first();
//手机号不存在,或者密码错误
if (! $user?->verifyPassword($input['password'])) {
throw new BizException(__('Incorrect account or password'));
}
break;
case 'sms-code'://手机号+验证码
app(SmsCodeService::class)->validate(
$input['phone'],
SmsCode::TYPE_REGISTER,
$input['verify_code']
);
$user = User::where('phone', $phone)->first();
break;
case 'wechat-mini'://微信小程序解密手机号
//解密失败
$app = EasyWeChatFactory::miniProgram([
'app_id' => config('wechat.mini_program.default.app_id', ''),
'secret' => config('wechat.mini_program.default.secret', ''),
// 下面为可选项
// 指定 API 调用返回结果的类型array(default)/collection/object/raw/自定义类名
'response_type' => 'array',
'log' => [
'level' => 'debug',
'file' => storage_path('logs/wechat-mini.log'),
],
]);
$session = Cache::get($socialite['socialite_id']);
try {
$decryptedData = $app->encryptor->decryptData($session, $input['iv'], $input['data']);
} catch (\EasyWeChat\Kernel\Exceptions\DecryptException $e) {
report($e);
throw new BizException('系统错误, 请重新进入小程序');
}
$phone = data_get($decryptedData, 'phoneNumber');
//解密成功,$user
$user = User::where('phone', $phone)->first();
break;
}
if (empty($phone)) {
throw new BizException('系统错误,未找到手机号');
}
//走登录逻辑
if ($user) {
$token = $this->loginUser($user, $request);
} else {//走注册逻辑
$time = now();
$ip = $request->realIp();
$inviter = $this->findUserByCode((string) Arr::get($input, 'inviter_code'));
try {
DB::beginTransaction();
$user = User::create([
'phone' => $phone,
'password' => Str::random(6), //先随机一个密码
'phone_verified_at' => $time,
'register_ip' => $ip,
'last_login_at' => $time,
'last_login_ip' => $ip,
],
$inviter
);
DB::commit();
} catch (Throwable $e) {
DB::rollBack();
report($e);
throw new BizException(__('Registration failed, please try again'));
}
$token = $user->createToken(Device::UNIAPP, ['mall']);
}
//解绑对应三方以前的关系
SocialiteUser::where([
'user_id' => $user->id,
'socialite_type' => $socialite['socialite_type'],
])->update([
'user_id' => null,
]);
//绑定用户和三方信息关系
$socialiteUser->update([
'user_id' => $user->id,
]);
return $token;
}
/**
* 第三方登录现有绑定的用户
*
* @param [User] $user
* @param [Request] $request
*/
protected function loginUser(User $user, Request $request)
{
$token = null;
$user->last_login_at = now();
$user->last_login_ip = $request->realIp();
$user->save();
// 获取登录设备
$device = $request->header('client-app', Device::UNIAPP);
$device = Device::UNIAPP;
// 清理此用户的商城端令牌
$user->tokens()->where('name', $device)->delete();
// 颁发新的商城端令牌
$token = $user->createToken($device, ['mall']);
return $token;
}
/**
* 通过邀请码搜索用户
*
* @param string $code
* @return \App\Models\User|null
*
* @throws \App\Exceptions\BizException
*/
protected function findUserByCode(string $code): ?User
{
if ($code === '') {
return null;
}
$user = User::when(PhoneNumber::validate($code), function ($query) use ($code) {
$query->where('phone', $code);
}, function ($query) use ($code) {
$query->whereRelation('userInfo', 'code', $code);
})->first();
if ($user === null) {
throw new BizException(__('Inviter does not exist'));
}
return $user;
}
}