This commit is contained in:
Your Name
2026-08-22 10:46:13 +08:00
parent c06d293424
commit 43e5411b6a
39 changed files with 1607 additions and 300 deletions
@@ -740,7 +740,9 @@ class WecomPromotionLogic
'agent_id' => $agentId,
'secret_configured' => trim((string) config('qywx_customer_acquisition.secret', '')) !== '',
'callback_ready' => $callbackTokenConfigured && $callbackAesConfigured,
'callback_url' => rtrim($domain, '/') . '/api/qywx/external-contact/notify',
// 复用自建应用现有的「API 接收消息」入口;获客助手事件与其他应用事件
// 由同一个控制器按 Event/ChangeType 分发,不需要再配置第二个回调地址。
'callback_url' => rtrim($domain, '/') . '/api/QywxExternalContactCallback/notify',
'official_doc' => 'https://developer.work.weixin.qq.com/document/path/97297',
];
}
@@ -27,7 +27,9 @@ class DesktopWorkstationLogic extends BaseLogic
{
public const CONFIG_TYPE = 'desktop_workstation';
public const PLATFORMS = ['windows_x64', 'macos_arm64', 'macos_x64'];
public const PLATFORMS = ['windows_x64', 'macos_arm64', 'macos_x64'];
public const PACKAGE_TYPES = ['archive', 'inno_setup'];
/**
* @notes 管理端读取配置(安装包地址补全域名)
@@ -76,10 +78,10 @@ class DesktopWorkstationLogic extends BaseLogic
$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();
$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;
@@ -186,8 +188,9 @@ class DesktopWorkstationLogic extends BaseLogic
$packages[$key] = [
'url' => (string) ($params[$key . '_url'] ?? ''),
'sha256' => (string) ($params[$key . '_sha256'] ?? ''),
'size' => $params[$key . '_size'] ?? 0,
'filename' => (string) ($params[$key . '_filename'] ?? ''),
'size' => $params[$key . '_size'] ?? 0,
'filename' => (string) ($params[$key . '_filename'] ?? ''),
'type' => (string) ($params[$key . '_type'] ?? 'archive'),
];
}
}
@@ -210,7 +213,7 @@ class DesktopWorkstationLogic extends BaseLogic
{
$packages = [];
foreach (self::PLATFORMS as $key) {
$packages[$key] = self::publicPackage($config['packages'][$key] ?? []);
$packages[$key] = self::publicPackage($config['packages'][$key] ?? [], $key);
}
$config['packages'] = $packages;
return $config;
@@ -230,8 +233,9 @@ class DesktopWorkstationLogic extends BaseLogic
$url = FileService::setFileUrl($url);
}
$sha256 = strtolower(trim((string) ($row['sha256'] ?? '')));
$filename = trim((string) ($row['filename'] ?? ''));
$size = (int) ($row['size'] ?? 0);
$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'];
@@ -242,8 +246,9 @@ class DesktopWorkstationLogic extends BaseLogic
$normalized[$key] = [
'url' => $url,
'sha256' => $sha256,
'size' => max(0, $size),
'filename' => mb_substr($filename, 0, 180),
'size' => max(0, $size),
'filename' => mb_substr($filename, 0, 180),
'type' => $type,
];
}
return $normalized;
@@ -251,36 +256,52 @@ class DesktopWorkstationLogic extends BaseLogic
/**
* @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);
* @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}
*/
private static function plainPackage(array $row): array
{
return [
* @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'] ?? ''),
'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}
*/
private static function emptyPackage(): array
{
return ['url' => '', 'sha256' => '', 'size' => 0, 'filename' => ''];
}
* @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}
@@ -120,14 +120,27 @@ class DesktopWorkstationValidate extends BaseValidate
unset($data);
$url = trim((string) ($row['url'] ?? ''));
$sha256 = strtolower(trim((string) ($row['sha256'] ?? '')));
$filename = trim((string) ($row['filename'] ?? ''));
$size = $row['size'] ?? 0;
$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;
$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 路径';
}
@@ -40,11 +40,12 @@ $config = [
'title' => '医生工作站 0.2.0',
'notes' => '稳定性更新',
'packages' => [
'windows_x64' => [
'url' => 'https://cdn.example.com/DoctorWorkstation-Windows-x64-0.2.0.zip',
'sha256' => $sha,
'size' => 123,
'filename' => 'DoctorWorkstation-Windows-x64-0.2.0.zip',
'windows_x64' => [
'url' => 'https://cdn.example.com/DoctorWorkstation-Setup-Windows-x64-0.2.0.exe',
'sha256' => $sha,
'size' => 123,
'filename' => 'DoctorWorkstation-Setup-Windows-x64-0.2.0.exe',
'type' => 'inno_setup',
],
],
];
@@ -53,10 +54,12 @@ $optional = DesktopWorkstationLogic::evaluate($config, '0.1.8', 'windows', 'x64'
desktopUpdateExpect($optional['has_update'] === true, 'newer published version is an update');
desktopUpdateExpect($optional['force'] === false, 'force stays off when above min version');
desktopUpdateExpect($optional['can_install'] === true, 'hashed package can be installed');
desktopUpdateExpect(
is_array($optional['package']) && $optional['package']['sha256'] === $sha,
'matching windows package is returned'
);
desktopUpdateExpect(
is_array($optional['package'])
&& $optional['package']['sha256'] === $sha
&& $optional['package']['type'] === 'inno_setup',
'matching Windows installer package is returned with its explicit type'
);
$forcedByMin = DesktopWorkstationLogic::evaluate($config, '0.1.0', 'windows', 'x64');
desktopUpdateExpect($forcedByMin['force'] === true, 'below min version forces upgrade');
@@ -90,10 +93,11 @@ desktopUpdateExpect(
$adminView = file_get_contents(dirname(__DIR__, 2) . '/admin/src/views/setting/desktop_workstation/index.vue');
desktopUpdateExpect(is_string($adminView), 'admin view source is readable');
desktopUpdateExpect(
str_contains($adminView, 'setting.desktop_workstation/setConfig')
&& str_contains($adminView, 'force_update'),
'admin page can save force-update configuration'
);
str_contains($adminView, 'setting.desktop_workstation/setConfig')
&& str_contains($adminView, 'force_update')
&& str_contains($adminView, 'inno_setup'),
'admin page can save force-update and Inno Setup configuration'
);
$migration = file_get_contents(
dirname(__DIR__) . '/sql/1.9.20260821/add_desktop_workstation_update_menu.sql'