$this->format(DB::table('site_settings')->where('id', 1)->sharedLock()->first())); } public function save(array $input): array { $data = Validator::make($input, [ 'translations' => ['present', 'array:zh,en,vi'], 'translations.*' => ['array:content'], 'translations.*.content' => ['nullable', 'string', 'max:2000'], 'domains' => ['present', 'array', 'max:100'], 'domains.*' => ['required', 'string', 'max:2048'], ], [ 'translations.present' => '请提交三语言跑马灯内容', 'translations.array' => '跑马灯仅支持zh/en/vi三种语言', 'translations.*.array' => '每种语言请提交content对象', 'translations.*.content.string' => '跑马灯内容必须为文本', 'translations.*.content.max' => '每种语言的跑马灯最多2000个字符', 'domains.present' => '请提交域名数组', 'domains.array' => '域名必须为数组', 'domains.max' => '域名最多100个', 'domains.*.required' => '域名不能为空', 'domains.*.string' => '每个域名必须为字符串', 'domains.*.max' => '每个域名最多2048个字符', ])->validate(); if (array_values($data['domains']) !== $data['domains']) { throw ValidationException::withMessages(['domains' => '域名必须为顺序数组']); } $domains = []; foreach ($data['domains'] as $index => $domain) { $url = $this->domain($domain, $index); if (!in_array($url, $domains, true)) $domains[] = $url; } $translations = []; foreach (['zh', 'en', 'vi'] as $language) $translations[$language] = ['content' => trim((string) ($data['translations'][$language]['content'] ?? ''))]; $payload = ['translations' => $translations, 'domains' => $domains]; return DB::transaction(function () use ($payload) { DB::table('site_settings')->insertOrIgnore(['id' => 1, 'domains' => '[]', 'created_at' => now(), 'updated_at' => now()]); $before = $this->format(DB::table('site_settings')->where('id', 1)->lockForUpdate()->first()); if ($before !== $payload) { DB::table('site_settings')->where('id', 1)->update([ 'domains' => json_encode($payload['domains'], JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR), 'updated_at' => now()]); DB::table('site_setting_translations')->where('site_setting_id', 1)->delete(); foreach ($payload['translations'] as $language => $text) { if ($text['content'] !== '') DB::table('site_setting_translations')->insert([ 'site_setting_id' => 1, 'language' => $language, 'content' => $text['content']]); } OperationAuditService::record('site_configuration', 1, 'update', $before, $payload); } return $payload; }, 3); } private function format(?object $row): array { $domains = $row ? json_decode($row->domains, true, 512, JSON_THROW_ON_ERROR) : []; if (!is_array($domains) || array_values($domains) !== $domains || count(array_filter($domains, 'is_string')) !== count($domains)) { throw new \RuntimeException('站点域名配置格式异常'); } $translations = ['zh' => ['content' => ''], 'en' => ['content' => ''], 'vi' => ['content' => '']]; if ($row) foreach (DB::table('site_setting_translations')->where('site_setting_id', 1)->get(['language', 'content']) as $text) { if (isset($translations[$text->language])) $translations[$text->language] = ['content' => (string) $text->content]; } return ['translations' => $translations, 'domains' => $domains]; } private function domain(string $value, int $index): string { $url = trim($value); if (!preg_match('~^[a-z][a-z0-9+.-]*://~i', $url)) $url = 'https://' . $url; $parts = parse_url($url); if (!filter_var($url, FILTER_VALIDATE_URL) || !$parts || !in_array(strtolower($parts['scheme'] ?? ''), ['http', 'https'], true) || empty($parts['host']) || isset($parts['user']) || isset($parts['pass']) || isset($parts['query']) || isset($parts['fragment']) || !in_array($parts['path'] ?? '', ['', '/'], true) || (isset($parts['port']) && $parts['port'] < 1)) { throw ValidationException::withMessages(['domains.' . $index => '域名仅支持HTTP/HTTPS站点地址,不带账号、路径、查询参数或片段']); } return strtolower($parts['scheme']) . '://' . strtolower($parts['host']) . (isset($parts['port']) ? ':' . $parts['port'] : ''); } }