233 lines
8.0 KiB
PHP
233 lines
8.0 KiB
PHP
<?php
|
||
|
||
namespace App\Services;
|
||
|
||
use App\Models\ServerResource;
|
||
use Illuminate\Http\Client\ConnectionException;
|
||
use Illuminate\Http\Client\RequestException;
|
||
use Illuminate\Support\Facades\Http;
|
||
use Illuminate\Validation\ValidationException;
|
||
|
||
class ServerUserManagementClient
|
||
{
|
||
public function users(ServerResource $server): array
|
||
{
|
||
return $this->request($server, 'get', '/users')->json();
|
||
}
|
||
|
||
public function user(ServerResource $server, string $username): array
|
||
{
|
||
return $this->request($server, 'get', '/users/'.$this->encodePath($username))->json();
|
||
}
|
||
|
||
public function userExists(ServerResource $server, string $username): bool
|
||
{
|
||
try {
|
||
$this->user($server, $username);
|
||
|
||
return true;
|
||
} catch (ValidationException $exception) {
|
||
$errors = $exception->errors();
|
||
$messages = collect($errors)->flatten()->map(fn ($message): string => (string) $message);
|
||
if ($messages->contains(fn (string $message): bool => str_contains(mb_strtolower($message), 'not found') || str_contains($message, '不存在'))) {
|
||
return false;
|
||
}
|
||
|
||
throw $exception;
|
||
}
|
||
}
|
||
|
||
public function createUser(ServerResource $server, array $payload): array
|
||
{
|
||
return $this->request($server, 'post', '/users', $payload)->json();
|
||
}
|
||
|
||
public function deleteUser(ServerResource $server, string $username): array
|
||
{
|
||
return $this->request($server, 'delete', '/users/'.$this->encodePath($username))->json();
|
||
}
|
||
|
||
public function updatePassword(ServerResource $server, string $username, string $passwordHash): array
|
||
{
|
||
return $this->request($server, 'patch', '/users/'.$this->encodePath($username).'/password', [
|
||
'password_hash' => $passwordHash,
|
||
])->json();
|
||
}
|
||
|
||
public function userEnvironment(ServerResource $server, string $username): array
|
||
{
|
||
return $this->request($server, 'get', '/users/'.$this->encodePath($username).'/environment')->json();
|
||
}
|
||
|
||
public function updateUserEnvironment(ServerResource $server, string $username, string $content): array
|
||
{
|
||
return $this->request($server, 'put', '/users/'.$this->encodePath($username).'/environment', [
|
||
'content' => $content,
|
||
])->json();
|
||
}
|
||
|
||
public function updateAllUserEnvironments(ServerResource $server, string $content): array
|
||
{
|
||
return $this->request($server, 'put', '/users/environment', [
|
||
'content' => $content,
|
||
])->json();
|
||
}
|
||
|
||
public function groups(ServerResource $server): array
|
||
{
|
||
return $this->request($server, 'get', '/groups')->json();
|
||
}
|
||
|
||
public function createGroup(ServerResource $server, string $groupname): array
|
||
{
|
||
return $this->request($server, 'post', '/groups', ['groupname' => $groupname])->json();
|
||
}
|
||
|
||
public function deleteGroup(ServerResource $server, string $groupname): array
|
||
{
|
||
return $this->request($server, 'delete', '/groups/'.$this->encodePath($groupname))->json();
|
||
}
|
||
|
||
public function userGroups(ServerResource $server, string $username): array
|
||
{
|
||
return $this->request($server, 'get', '/users/'.$this->encodePath($username).'/groups')->json();
|
||
}
|
||
|
||
public function syncUserGroups(ServerResource $server, string $username, array $groups, string $mode = 'replace'): array
|
||
{
|
||
return $this->request($server, 'post', '/users/'.$this->encodePath($username).'/groups', [
|
||
'groups' => array_values($groups),
|
||
'mode' => $mode,
|
||
])->json();
|
||
}
|
||
|
||
public function removeUserGroups(ServerResource $server, string $username, array $groups): array
|
||
{
|
||
return $this->request($server, 'delete', '/users/'.$this->encodePath($username).'/groups', [
|
||
'groups' => array_values($groups),
|
||
'mode' => 'append',
|
||
])->json();
|
||
}
|
||
|
||
private function request(ServerResource $server, string $method, string $path, array $payload = [])
|
||
{
|
||
$target = $this->resolveServer($server);
|
||
$baseUrl = rtrim((string) $target->user_api_base_url, '/');
|
||
$token = trim((string) $target->user_api_token);
|
||
|
||
if ($baseUrl === '' || $token === '') {
|
||
throw ValidationException::withMessages([
|
||
'server' => ['该服务器未配置用户管理 API 地址或密钥。'],
|
||
]);
|
||
}
|
||
|
||
try {
|
||
$pending = Http::baseUrl($baseUrl)
|
||
->acceptJson()
|
||
->asJson()
|
||
->timeout((int) config('services.server_user_management.timeout', 15))
|
||
->connectTimeout((int) config('services.server_user_management.connect_timeout', 5))
|
||
->retry(2, 200, throw: false)
|
||
->withToken($token)
|
||
->withOptions([
|
||
'verify' => (bool) config('services.server_user_management.verify_ssl', false),
|
||
]);
|
||
|
||
$response = match ($method) {
|
||
'post' => $pending->post($path, $payload),
|
||
'put' => $pending->put($path, $payload),
|
||
'patch' => $pending->patch($path, $payload),
|
||
'delete' => empty($payload) ? $pending->delete($path) : $pending->send('DELETE', $path, ['json' => $payload]),
|
||
default => $pending->get($path),
|
||
};
|
||
} catch (ConnectionException|RequestException $exception) {
|
||
throw ValidationException::withMessages([
|
||
'server' => ['服务器用户管理 API 调用失败:'.$exception->getMessage()],
|
||
]);
|
||
}
|
||
|
||
if (! $response->successful()) {
|
||
$message = $this->errorMessage($response->json());
|
||
|
||
throw ValidationException::withMessages([
|
||
'server' => [$message],
|
||
]);
|
||
}
|
||
|
||
return $response;
|
||
}
|
||
|
||
private function resolveServer(ServerResource $server): ServerResource
|
||
{
|
||
if (! $server->parent_id) {
|
||
return $server;
|
||
}
|
||
|
||
return $server->parent()->firstOrFail();
|
||
}
|
||
|
||
private function encodePath(string $value): string
|
||
{
|
||
return rawurlencode($value);
|
||
}
|
||
|
||
private function errorMessage(mixed $payload): string
|
||
{
|
||
if (! is_array($payload)) {
|
||
return '服务器用户管理 API 返回异常';
|
||
}
|
||
|
||
$message = data_get($payload, 'message')
|
||
?: data_get($payload, 'detail.message')
|
||
?: data_get($payload, 'detail');
|
||
|
||
if (is_string($message) && $message !== '') {
|
||
return $message;
|
||
}
|
||
|
||
if (is_array($message)) {
|
||
$messages = collect($message)
|
||
->map(function (mixed $item): string {
|
||
if (is_string($item)) {
|
||
return $item;
|
||
}
|
||
|
||
if (is_array($item)) {
|
||
return (string) (data_get($item, 'message')
|
||
?: data_get($item, 'msg')
|
||
?: data_get($item, 'detail')
|
||
?: json_encode($item, JSON_UNESCAPED_UNICODE));
|
||
}
|
||
|
||
return '';
|
||
})
|
||
->filter()
|
||
->values();
|
||
|
||
if ($messages->isNotEmpty()) {
|
||
return $messages->join(';');
|
||
}
|
||
}
|
||
|
||
$failedUsers = collect(data_get($payload, 'failed_users', []))
|
||
->map(function (mixed $item): string {
|
||
if (! is_array($item)) {
|
||
return '';
|
||
}
|
||
|
||
$username = (string) data_get($item, 'username', '');
|
||
$error = (string) (data_get($item, 'message') ?: data_get($item, 'code', ''));
|
||
|
||
return trim($username.($error !== '' ? ': '.$error : ''));
|
||
})
|
||
->filter()
|
||
->values();
|
||
|
||
if ($failedUsers->isNotEmpty()) {
|
||
return '部分服务器用户环境变量设置失败:'.$failedUsers->join(';');
|
||
}
|
||
|
||
return '服务器用户管理 API 返回异常';
|
||
}
|
||
}
|