provider->exchange(Fields::text($input, 'code', 512)); return $this->issue($identity); } /** Internal only. The HTTP controller never accepts provider identity from clients. */ public function issue(array $identity): array { return (new MiniUser())->getConnection()->transaction(function () use ($identity) { $now = date('Y-m-d H:i:s'); MiniUser::query()->insertOrIgnore($identity + [ 'nickname' => '', 'status' => 1, 'created_at' => $now, 'updated_at' => $now, ]); return $this->sessionFor(MiniUser::query()->where($identity)->lockForUpdate()->firstOrFail()); }); } /** * Issues a session for an account that already exists, so a verified phone reaches the same * user and therefore the same granted identity as the WeChat login it was bound to. */ public function issueForExisting(MiniUser $user): array { return (new MiniUser())->getConnection()->transaction(function () use ($user) { return $this->sessionFor(MiniUser::query()->where('id', $user->id)->lockForUpdate()->firstOrFail()); }); } private function sessionFor(MiniUser $user): array { if ((int) $user->status !== 1) { throw new ApiException(40302, '账号已停用', 403); } $now = date('Y-m-d H:i:s'); $token = bin2hex(random_bytes(32)); $expires = date('Y-m-d H:i:s', time() + (int) config('mini_api.session_ttl', 604800)); MiniSession::query()->insert([ 'user_id' => $user->id, 'token_hash' => hash('sha256', $token), 'expires_at' => $expires, 'created_at' => $now, ]); return [ 'access_token' => $token, 'token_type' => 'Bearer', 'expires_at' => $expires, 'user_id' => (string) $user->id, 'identities' => (new Access(new Principal((string) $user->id)))->identities(), ]; } public function logout(string $token): array { MiniSession::query()->where('token_hash', hash('sha256', $token))->whereNull('revoked_at')->update(['revoked_at' => date('Y-m-d H:i:s')]); return ['logged_out' => true]; } }