354 lines
13 KiB
PHP
354 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'];
|
|
|
|
public const PACKAGE_TYPES = ['archive', 'inno_setup'];
|
|
|
|
/**
|
|
* @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] ?? [], $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;
|
|
}
|
|
}
|