request->get('imei', ''); $version = $this->request->get('version', ''); $build = $this->request->get('build', ''); if (empty($imei)) { $this->error('设备标识不能为空'); } // 记录检查日志 Log::info("OTA检查: imei={$imei}, version={$version}, build={$build}"); // 查询最新已发布版本 $latestVersion = OtaVersion::where('status', 'published') ->order('version_code', 'desc') ->find(); if (!$latestVersion) { $this->error('暂无可用版本'); } // 转换为整数比较版本号 $localBuild = intval($build); $serverBuild = intval($latestVersion['version_code']); // 如果云端版本不高于本地版本,返回无更新 if ($serverBuild <= $localBuild) { $this->success('已是最新版本', null); } // 检查最低版本限制 if (!empty($latestVersion['min_version'])) { // 简单字符串比较版本名 if (version_compare($version, $latestVersion['min_version'], '<')) { $this->error('当前版本过低,请先升级到 ' . $latestVersion['min_version']); } } // 检查设备白名单(如果设置了 target_devices) if (!empty($latestVersion['target_devices'])) { $targets = array_map('trim', explode(',', $latestVersion['target_devices'])); if (!in_array($imei, $targets)) { $this->error('该设备不在此次升级范围内'); } } // 构建返回数据 $apkUrl = $this->buildApkUrl($latestVersion['apk_file']); $sha256 = $latestVersion['sha256'] ?: ''; // 如果 sha256 为空或 fileSize 为空,尝试从文件计算 if (empty($sha256) || empty($latestVersion['file_size'])) { $fileInfo = $this->calculateFileInfo($latestVersion['apk_file']); if ($fileInfo) { if (empty($sha256)) { $sha256 = $fileInfo['sha256']; // 可选:自动更新数据库 // OtaVersion::update(['sha256' => $sha256], ['id' => $latestVersion['id']]); } if (empty($latestVersion['file_size'])) { $latestVersion['file_size'] = $fileInfo['file_size']; } } } $data = [ 'versionName' => $latestVersion['version_name'], 'versionCode' => (int)$latestVersion['version_code'], 'downloadUrl' => $apkUrl, 'updateLog' => $latestVersion['update_log'], 'fileSize' => (int)($latestVersion['file_size'] ?? 0), 'sha256' => $sha256, 'isForceUpdate' => (bool)$latestVersion['is_force_update'], ]; $this->success('发现新版本', $data); } /** * 构建 APK 下载 URL * * 客户端要求必须使用 HTTPS 协议,此处强制转换为 HTTPS。 * * @param string $relativePath 相对路径 * @return string 完整 HTTPS URL */ private function buildApkUrl($relativePath) { $domain = request()->domain(); // 强制使用 HTTPS,客户端要求 downloadUrl 必须以 https:// 开头 $domain = preg_replace('/^http:\/\//i', 'https://', $domain); return rtrim($domain, '/') . '/uploads/' . ltrim($relativePath, '/'); } /** * 计算 APK 文件的 SHA-256 和文件大小 * * @param string $relativePath 相对路径 * @return array|null ['sha256' => string, 'file_size' => int] */ private function calculateFileInfo($relativePath) { $fullPath = ROOT_PATH . 'public' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . ltrim($relativePath, '/'); if (!file_exists($fullPath)) { return null; } $fileSize = filesize($fullPath); $sha256 = hash_file('sha256', $fullPath); return [ 'sha256' => $sha256, 'file_size' => $fileSize, ]; } }