307 lines
9.7 KiB
PHP
307 lines
9.7 KiB
PHP
<?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;
|
||
}
|
||
}
|