'in:0,1|checkEnabledRequiresVersion', 'force_update' => 'in:0,1', 'latest_version' => 'max:20|checkVersion', 'min_version' => 'max:20|checkVersion', 'title' => 'max:80', 'notes' => 'max:4000', 'packages' => 'checkPackages', ]; protected $message = [ 'enabled.in' => '启用状态不正确', 'force_update.in' => '强制升级开关不正确', 'latest_version.max' => '最新版本号过长', 'min_version.max' => '最低版本号过长', 'title.max' => '更新标题最多 80 个字符', 'notes.max' => '更新说明最多 4000 个字符', 'packages.array' => '安装包配置格式不正确', ]; /** * @param mixed $value * @return bool|string */ protected function checkVersion($value) { $value = trim((string) $value); if ($value === '') { return true; } if (DesktopWorkstationLogic::normalizeVersion($value) === '') { return '版本号须为 x.y.z 数字格式,例如 0.2.0'; } return true; } /** * @param mixed $value * @param mixed $rule * @param array $data * @return bool|string */ protected function checkEnabledRequiresVersion($value, $rule, array $data = []) { unset($rule); $enabled = in_array((string) $value, ['1', 'true'], true); $latest = trim((string) ($data['latest_version'] ?? '')); if ($enabled && $latest === '') { return '启用自动检测时请填写最新版本号'; } return true; } /** * @param mixed $value * @param mixed $rule * @param array $data * @return bool|string */ protected function checkPackages($value, $rule, array $data = []) { unset($rule); $enabled = (string) ($data['enabled'] ?? '0'); $latest = trim((string) ($data['latest_version'] ?? '')); if (in_array($enabled, ['1', 'true'], true) && $latest === '') { return '启用自动检测时请填写最新版本号'; } if ($value === '' || $value === null) { return true; } if (!is_array($value)) { return '安装包配置格式不正确'; } foreach (DesktopWorkstationLogic::PLATFORMS as $key) { $row = $value[$key] ?? []; if ($row === '' || $row === null) { continue; } if (!is_array($row)) { return '安装包配置格式不正确'; } $error = $this->checkPackageRow($key, $row, $data); if ($error !== true) { return $error; } } return true; } /** * @param array $row * @param array $data * @return bool|string */ private function checkPackageRow(string $key, array $row, array $data) { unset($data); $url = trim((string) ($row['url'] ?? '')); $sha256 = strtolower(trim((string) ($row['sha256'] ?? ''))); $filename = trim((string) ($row['filename'] ?? '')); $type = strtolower(trim((string) ($row['type'] ?? 'archive'))); $size = $row['size'] ?? 0; $labels = [ 'windows_x64' => 'Windows 64 位', 'macos_arm64' => 'macOS Apple 芯片', 'macos_x64' => 'macOS Intel', ]; $label = $labels[$key] ?? $key; if (!in_array($type, DesktopWorkstationLogic::PACKAGE_TYPES, true)) { return $label . '安装包类型不受支持'; } if ($type === 'inno_setup' && $key !== 'windows_x64') { return $label . '不能使用 Windows Inno Setup 安装包'; } if ($type === 'inno_setup' && $filename !== '' && !str_ends_with(strtolower($filename), '.exe')) { return $label . ' Inno Setup 文件名必须以 .exe 结尾'; } if ($type === 'inno_setup' && preg_match('#^http://#i', $url)) { return $label . '自动安装程序必须使用 HTTPS 下载地址'; } if ($url !== '' && !$this->isAllowedPackageUrl($url)) { return $label . '安装包地址必须是 http(s) 链接或站内 uploads 路径'; } if ($sha256 !== '' && !preg_match('/^[a-f0-9]{64}$/', $sha256)) { return $label . ' SHA-256 须为 64 位十六进制'; } if ($url !== '' && $sha256 === '' && preg_match('#^https?://#i', $url)) { return $label . '使用外部下载地址时必须填写 SHA-256,避免安装被篡改的文件'; } if ($filename !== '' && strlen($filename) > 180) { return $label . '文件名过长'; } if ($size !== '' && $size !== null && (!is_numeric($size) || (int) $size < 0)) { return $label . '文件大小不正确'; } return true; } private function isAllowedPackageUrl(string $url): bool { if (preg_match('#^https?://#i', $url)) { return filter_var($url, FILTER_VALIDATE_URL) !== false; } return (bool) preg_match('#^(uploads|resource)/#', str_replace('\\', '/', $url)); } }