Files
zyt/server/app/adminapi/logic/setting/DesktopWorkstationLogic.php
T
2026-08-22 08:51:35 +08:00

333 lines
13 KiB
PHP

<?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'];
/**
* @notes 管理端读取配置(安装包地址补全域名)
*/
public static function getConfig(): array
{
return self::present(self::loadStored());
}
/**
* @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] ?? [])
: 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'] ?? ''),
];
}
}
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] ?? []);
}
$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);
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),
];
}
return $normalized;
}
/**
* @param array<string, mixed> $row
* @return array{url:string,sha256:string,size:int,filename:string}
*/
private static function publicPackage(array $row): array
{
$plain = self::plainPackage($row);
$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}
*/
private static function plainPackage(array $row): array
{
return [
'url' => trim((string) ($row['url'] ?? '')),
'sha256' => strtolower(trim((string) ($row['sha256'] ?? ''))),
'size' => max(0, (int) ($row['size'] ?? 0)),
'filename' => (string) ($row['filename'] ?? ''),
];
}
/**
* @return array{url:string,sha256:string,size:int,filename:string}
*/
private static function emptyPackage(): array
{
return ['url' => '', 'sha256' => '', 'size' => 0, 'filename' => ''];
}
/**
* @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;
}
}