| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687 |
- <?php
- namespace App\Services;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Validator;
- use Illuminate\Validation\ValidationException;
- class SiteConfigurationService
- {
- public function get(): array
- {
- return DB::transaction(fn () => $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'] : '');
- }
- }
|