Files
zyt/server/app/adminapi/logic/setting/DesktopWorkstationLogic.php

384 lines
15 KiB
PHP
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
// +----------------------------------------------------------------------
// | likeadmin快速开发前后端分离管理后台(PHP版)
// +----------------------------------------------------------------------
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
// | 开源版本可自由商用,可去除界面版权logo
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
// | github下载:https://github.com/likeshop-github/likeadmin
// | 访问官网:https://www.likeadmin.cn
// | likeadmin团队 版权所有 拥有最终解释权
// +----------------------------------------------------------------------
// | author: likeadminTeam
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace app\adminapi\logic\setting;
use app\common\logic\BaseLogic;
use app\common\service\ConfigService;
use app\common\service\FileService;
/**
* 医生工作站桌面端升级配置
*/
class DesktopWorkstationLogic extends BaseLogic
{
public const CONFIG_TYPE = 'desktop_workstation';
public const PLATFORMS = ['windows_x64', 'macos_arm64', 'macos_x64'];
public const PACKAGE_TYPES = ['archive', 'inno_setup'];
/**
* @notes 管理端读取配置(安装包地址补全域名)
*/
public static function getConfig(): array
{
return self::present(self::loadStored());
}
/** 医生公告使用的安装包清单,与自动升级检测开关独立。 */
public static function downloads(): array
{
return self::downloadCatalog(self::getConfig());
}
public static function downloadCatalog(array $config): array
{
$labels = [
'windows_x64' => 'Windows 64位',
'macos_arm64' => 'macOS(Apple芯片)',
'macos_x64' => 'macOS(Intel芯片)',
];
$packages = [];
foreach (self::PLATFORMS as $platform) {
$package = self::plainPackage((array) ($config['packages'][$platform] ?? []), $platform);
$url = parse_url($package['url']);
if (!is_array($url) || !in_array(strtolower($url['scheme'] ?? ''), ['https', 'http'], true)
|| empty($url['host']) || isset($url['user']) || isset($url['pass'])
|| !preg_match('/^[a-f0-9]{64}$/', $package['sha256'])) {
continue;
}
$packages[] = ['platform' => $platform, 'label' => $labels[$platform]] + $package;
}
return [
'version' => self::normalizeVersion((string) ($config['latest_version'] ?? '')),
'packages' => $packages,
];
}
/**
* @notes 保存升级策略与各平台安装包
*/
public static function setConfig(array $params): bool
{
$stored = self::normalizeInput($params);
ConfigService::set(self::CONFIG_TYPE, 'enabled', $stored['enabled']);
ConfigService::set(self::CONFIG_TYPE, 'latest_version', $stored['latest_version']);
ConfigService::set(self::CONFIG_TYPE, 'min_version', $stored['min_version']);
ConfigService::set(self::CONFIG_TYPE, 'force_update', $stored['force_update']);
ConfigService::set(self::CONFIG_TYPE, 'title', $stored['title']);
ConfigService::set(self::CONFIG_TYPE, 'notes', $stored['notes']);
ConfigService::set(self::CONFIG_TYPE, 'packages', $stored['packages']);
return true;
}
/**
* @notes 桌面端检测是否需要升级(免登录)
*/
public static function check(array $params): array
{
return self::evaluate(
self::present(self::loadStored()),
(string) ($params['current_version'] ?? ''),
(string) ($params['platform'] ?? ''),
(string) ($params['arch'] ?? '')
);
}
/**
* @notes 根据已发布配置计算客户端检测结果
* @param array $config getConfig() 形态的配置
*/
public static function evaluate(array $config, string $currentVersion, string $platform, string $arch): array
{
$current = self::normalizeVersion($currentVersion);
$latest = self::normalizeVersion((string) ($config['latest_version'] ?? ''));
$minVersion = self::normalizeVersion((string) ($config['min_version'] ?? ''));
$enabled = (int) ($config['enabled'] ?? 0) === 1;
$packageKey = self::packageKey($platform, $arch);
$package = $packageKey !== ''
? self::plainPackage($config['packages'][$packageKey] ?? [], $packageKey)
: self::emptyPackage();
$canInstall = $package['url'] !== '' && $package['sha256'] !== '';
$hasUpdate = $enabled && $latest !== '' && $current !== '' && self::compareVersion($current, $latest) < 0;
$belowMin = $minVersion !== '' && $current !== '' && self::compareVersion($current, $minVersion) < 0;
$wantsForce = $hasUpdate && (((int) ($config['force_update'] ?? 0) === 1) || $belowMin);
return [
'has_update' => $hasUpdate,
'force' => $hasUpdate && $wantsForce && $canInstall,
'enabled' => $enabled,
'current_version' => $current,
'latest_version' => $latest,
'min_version' => $minVersion,
'title' => (string) ($config['title'] ?? ''),
'notes' => (string) ($config['notes'] ?? ''),
'platform' => self::normalizePlatform($platform),
'arch' => self::normalizeArch($arch),
'package' => $canInstall ? $package : null,
'can_install' => $hasUpdate && $canInstall,
];
}
public static function compareVersion(string $left, string $right): int
{
return self::versionParts($left) <=> self::versionParts($right);
}
public static function normalizeVersion(string $version): string
{
$version = trim($version);
if ($version === '') {
return '';
}
if (!preg_match('/^\d+(?:\.\d+){0,3}$/', $version)) {
return '';
}
$parts = array_map(static fn(string $part): int => (int) $part, explode('.', $version));
$parts = array_pad(array_slice($parts, 0, 3), 3, 0);
return implode('.', $parts);
}
public static function packageKey(string $platform, string $arch): string
{
$os = self::normalizePlatform($platform);
$cpu = self::normalizeArch($arch);
if ($os === '' || $cpu === '') {
return '';
}
$key = $os . '_' . $cpu;
return in_array($key, self::PLATFORMS, true) ? $key : '';
}
public static function normalizePlatform(string $platform): string
{
$value = strtolower(trim($platform));
return match ($value) {
'windows', 'win', 'win32', 'win64' => 'windows',
'macos', 'mac', 'darwin', 'osx' => 'macos',
default => '',
};
}
public static function normalizeArch(string $arch): string
{
$value = strtolower(trim($arch));
return match ($value) {
'x64', 'amd64', 'x86_64', 'x86-64' => 'x64',
'arm64', 'aarch64' => 'arm64',
default => '',
};
}
/**
* @return array<string, mixed>
*/
private static function loadStored(): array
{
$packages = ConfigService::get(self::CONFIG_TYPE, 'packages', []);
if (!is_array($packages)) {
$packages = [];
}
return [
'enabled' => self::asFlag(ConfigService::get(self::CONFIG_TYPE, 'enabled', 1)),
'latest_version' => (string) (ConfigService::get(self::CONFIG_TYPE, 'latest_version', '') ?? ''),
'min_version' => (string) (ConfigService::get(self::CONFIG_TYPE, 'min_version', '') ?? ''),
'force_update' => self::asFlag(ConfigService::get(self::CONFIG_TYPE, 'force_update', 0)),
'title' => (string) (ConfigService::get(self::CONFIG_TYPE, 'title', '') ?? ''),
'notes' => (string) (ConfigService::get(self::CONFIG_TYPE, 'notes', '') ?? ''),
'packages' => self::normalizePackages($packages, persist: false),
];
}
/**
* @param array<string, mixed> $params
* @return array<string, mixed>
*/
public static function normalizeInput(array $params): array
{
$packages = $params['packages'] ?? [];
if (!is_array($packages)) {
$packages = [];
}
foreach (self::PLATFORMS as $key) {
if (!isset($packages[$key]) || !is_array($packages[$key])) {
$packages[$key] = [
'url' => (string) ($params[$key . '_url'] ?? ''),
'sha256' => (string) ($params[$key . '_sha256'] ?? ''),
'size' => $params[$key . '_size'] ?? 0,
'filename' => (string) ($params[$key . '_filename'] ?? ''),
'type' => (string) ($params[$key . '_type'] ?? 'archive'),
];
}
}
return [
'enabled' => self::asFlag($params['enabled'] ?? 0),
'latest_version' => self::normalizeVersion((string) ($params['latest_version'] ?? '')),
'min_version' => self::normalizeVersion((string) ($params['min_version'] ?? '')),
'force_update' => self::asFlag($params['force_update'] ?? 0),
'title' => mb_substr(trim((string) ($params['title'] ?? '')), 0, 80),
'notes' => mb_substr(trim((string) ($params['notes'] ?? '')), 0, 4000),
'packages' => self::normalizePackages($packages, persist: true),
];
}
/**
* @param array<string, mixed> $config
* @return array<string, mixed>
*/
private static function present(array $config): array
{
$packages = [];
foreach (self::PLATFORMS as $key) {
$packages[$key] = self::publicPackage($config['packages'][$key] ?? [], $key);
}
$config['packages'] = $packages;
return $config;
}
/**
* @param array<string, mixed> $packages
* @return array<string, array<string, mixed>>
*/
private static function normalizePackages(array $packages, bool $persist): array
{
$normalized = [];
foreach (self::PLATFORMS as $key) {
$row = is_array($packages[$key] ?? null) ? $packages[$key] : [];
$url = trim((string) ($row['url'] ?? ''));
if ($persist && $url !== '') {
$url = FileService::setFileUrl($url);
}
$sha256 = strtolower(trim((string) ($row['sha256'] ?? '')));
$filename = trim((string) ($row['filename'] ?? ''));
$size = (int) ($row['size'] ?? 0);
$type = self::normalizePackageType((string) ($row['type'] ?? ''), $key);
if ($persist) {
$filled = self::fillLocalPackageMeta($url, $sha256, $size, $filename);
$url = $filled['url'];
$sha256 = $filled['sha256'];
$size = $filled['size'];
$filename = $filled['filename'];
}
$normalized[$key] = [
'url' => $url,
'sha256' => $sha256,
'size' => max(0, $size),
'filename' => mb_substr($filename, 0, 180),
'type' => $type,
];
}
return $normalized;
}
/**
* @param array<string, mixed> $row
* @return array{url:string,sha256:string,size:int,filename:string,type:string}
*/
private static function publicPackage(array $row, string $key): array
{
$plain = self::plainPackage($row, $key);
$plain['url'] = $plain['url'] === '' ? '' : FileService::getFileUrl($plain['url']);
return $plain;
}
/**
* @param array<string, mixed> $row
* @return array{url:string,sha256:string,size:int,filename:string,type:string}
*/
private static function plainPackage(array $row, string $key = ''): array
{
return [
'url' => trim((string) ($row['url'] ?? '')),
'sha256' => strtolower(trim((string) ($row['sha256'] ?? ''))),
'size' => max(0, (int) ($row['size'] ?? 0)),
'filename' => (string) ($row['filename'] ?? ''),
'type' => self::normalizePackageType((string) ($row['type'] ?? ''), $key),
];
}
/**
* @return array{url:string,sha256:string,size:int,filename:string,type:string}
*/
private static function emptyPackage(): array
{
return ['url' => '', 'sha256' => '', 'size' => 0, 'filename' => '', 'type' => 'archive'];
}
public static function normalizePackageType(string $type, string $key = ''): string
{
$value = strtolower(trim($type));
if ($value === '') {
return 'archive';
}
if (!in_array($value, self::PACKAGE_TYPES, true)) {
return 'archive';
}
if ($value === 'inno_setup' && $key !== '' && $key !== 'windows_x64') {
return 'archive';
}
return $value;
}
/**
* @return array{url:string,sha256:string,size:int,filename:string}
*/
private static function fillLocalPackageMeta(string $url, string $sha256, int $size, string $filename): array
{
$relative = $url;
if ($relative !== '' && !preg_match('#^https?://#i', $relative)) {
$path = public_path() . ltrim(str_replace('\\', '/', $relative), '/');
if (is_file($path)) {
if ($sha256 === '' || !preg_match('/^[a-f0-9]{64}$/', $sha256)) {
$sha256 = hash_file('sha256', $path) ?: $sha256;
}
if ($size <= 0) {
$size = (int) filesize($path);
}
if ($filename === '') {
$filename = basename($path);
}
}
}
return [
'url' => $url,
'sha256' => strtolower($sha256),
'size' => $size,
'filename' => $filename,
];
}
/**
* @return array{0:int,1:int,2:int}
*/
private static function versionParts(string $version): array
{
$normalized = self::normalizeVersion($version);
if ($normalized === '') {
return [0, 0, 0];
}
return array_map('intval', explode('.', $normalized));
}
private static function asFlag(mixed $value): int
{
if (is_bool($value)) {
return $value ? 1 : 0;
}
return in_array((string) $value, ['1', 'true', 'on', 'yes'], true) ? 1 : 0;
}
}