where('phone', $phone)->where('status', 1)->exists()) { return ['sent' => true, 'expires_in' => SmsCode::ttl()]; } $code = SmsCode::issue($phone, $ip); try { $this->sender->send($phone, sprintf('您的验证码是 %s,%d 分钟内有效。请勿转发给他人。', $code, intdiv(SmsCode::ttl(), 60))); } catch (ApiException $error) { // A code the caller never received must not remain verifiable. SmsCode::discard($phone, $code); throw $error; } return ['sent' => true, 'expires_in' => SmsCode::ttl()]; } public function login(array $input): array { if (!SmsCode::enabled()) { throw new ApiException(50301, '短信登录未开启', 503); } Fields::only($input, ['phone', 'code']); $phone = self::mobile($input); SmsCode::consume($phone, Fields::text($input, 'code', 6)); $user = MiniUser::query()->where('phone', $phone)->first(); if (!$user || (int) $user->status !== 1) { throw new ApiException(40102, '验证码已失效,请重新获取', 401); } return (new AuthService())->issueForExisting($user); } /** SMS gateways only reach mainland mobile numbers; other formats never leave the server. */ public static function mobile(array $input): string { $phone = Fields::phone($input); if (!preg_match('/^1[3-9][0-9]{9}$/D', $phone)) { throw new ApiException(42201, '请填写有效的中国大陆手机号码', 422, ['phone' => '须为1开头的11位手机号']); } return $phone; } }