// +---------------------------------------------------------------------- declare (strict_types = 1); namespace addon\wxchat\middleware; use addon\wxchat\model\WxchatAppVersion as AppVersion; use think\facade\Log; use think\facade\Request; use think\Response; /** * AppVersionCheck 类 * * @author ywxapp */ class AppVersionCheck { /** * 处理请求 * * @param \think\Request $request * @param \Closure $next * @return Response */ // 需要跳过的路由(不需要版本检查的接口) protected array $except = [ 'app/version/check', 'app/version/latest', 'api/auth/login', 'api/auth/register', 'api/public/*', ]; // 需要强制版本检查的路由(版本过低直接拒绝) protected array $forceCheck = [ 'api/user/*', 'api/order/*', 'api/payment/*', 'api/wallet/*', ]; // 最低支持的版本号(硬性要求) protected array $minVersions = [ 'android' => 100, 'ios' => 100, 'harmony' => 100, ]; /** * 版本检查结果 */ protected array $result = [ 'need_update' => false, // 是否需要更新 'is_force_update' => false, // 是否强制更新 'is_compatible' => true, // 是否兼容 'update_type' => 'none', // 更新类型: none/optional/recommended/required 'current_version' => null, // 当前版本信息 'latest_version' => null, // 最新版本信息 'min_support_version' => 0, // 最低支持版本 'version_diff' => 0, // 版本差异 'message' => '', // 提示信息 'download_url' => '', // 下载地址 'update_content' => '', // 更新内容 'size' => '', // 安装包大小 ]; public function handle($request, \Closure $next) { // 获取Header信息 $platform = Request::header('App-Platform', Request::header('X-App-Platform', '')); $versionCode = (int) Request::header('App-Version-Code', Request::header('X-App-Version-Code', 0)); $versionName = Request::header('App-Version-Name', Request::header('X-App-Version-Name', '')); $channel = Request::header('App-Channel', Request::header('X-App-Channel', '')); // 记录请求信息(用于日志分析) // $this->logRequestInfo($platform, $versionCode, $versionName, $channel); // 检查是否需要跳过版本检查 // if ($this->shouldExcept()) { // return $next($request); // } // 验证Header必填字段 if (empty($platform) || $versionCode <= 0) { // 如果是强制检查的接口,直接拒绝 if ($this->isForceCheck()) { return $this->errorResponse( 400, '缺少APP版本信息,请升级到最新版本', ['required_headers' => ['App-Platform', 'App-Version-Code']] ); } // 非强制检查,记录警告但继续 Log::warning('请求缺少版本信息', [ 'path' => Request::url(), 'ip' => Request::ip(), ]); return $next($request); } // 验证平台合法性 if (! in_array($platform, ['android', 'ios', 'harmony', 'web'])) { return $this->errorResponse(400, '无效的平台类型'); } // 检查最低版本要求 if ($versionCode < ($this->minVersions[$platform] ?? 0)) { return $this->errorResponse( 426, // Upgrade Required "当前版本过低,请升级到最新版本", [ 'min_version_code' => $this->minVersions[$platform], 'current_version_code' => $versionCode, 'platform' => $platform, ] ); } // 执行版本检查逻辑 $checkResult = $this->checkVersion($platform, $versionCode); // 如果版本不兼容且是强制检查接口,拒绝访问 if (! $checkResult['is_compatible'] && $this->isForceCheck()) { return $this->errorResponse( 426, $checkResult['message'] ?? '版本不兼容,请升级到最新版本', [ 'need_update' => true, 'is_force_update' => true, 'latest_version' => $checkResult['latest_version'], 'download_url' => $checkResult['download_url'] ?? '', ] ); } // 将版本信息存入Request,供后续控制器使用 $request->appPlatform = $platform; $request->appVersionCode = $versionCode; $request->appVersionName = $versionName; $request->appChannel = $channel; // 继续执行 $response = $next($request); // 在响应Header中返回版本检查结果(可选) if ($checkResult['need_update']) { $response->header([ 'X-App-Need-Update' => 'true', 'X-App-Update-Type' => $checkResult['update_type'] ?? 'optional', 'X-App-Latest-Version' => $checkResult['latest_version']['version_name'] ?? '', ]); } return $response; } /** * 检查版本 * @param string $platform 平台: android/ios/harmony * @param int $currentVersionCode 当前版本号 * @return array * @throws Exception */ public function checkVersion(string $platform, int $currentVersionCode): array { // 验证平台 $validPlatforms = ['android', 'ios', 'harmony']; if (! in_array($platform, $validPlatforms)) { throw new Exception('无效的平台类型'); } // 获取最新版本 $latestVersion = AppVersion::getLatestVersion($platform); if (! $latestVersion) { throw new Exception('未找到该平台的版本信息'); } // 获取当前版本信息 $currentVersion = AppVersion::getVersion($platform, $currentVersionCode); // 设置最新版本信息 $this->result['latest_version'] = [ 'version_code' => $latestVersion->version_code, 'version_name' => $latestVersion->version_name, 'release_time' => $latestVersion->release_time, ]; // 设置当前版本信息 $this->result['current_version'] = $currentVersion ? [ 'version_code' => $currentVersion->version_code, 'version_name' => $currentVersion->version_name, ] : null; // 设置最低支持版本 $this->result['min_support_version'] = $latestVersion->min_support_version; // 计算版本差异 $this->result['version_diff'] = $latestVersion->version_code - $currentVersionCode; // 判断是否需要更新 if ($currentVersionCode < $latestVersion->version_code) { $this->result['need_update'] = true; $this->result['is_force_update'] = (bool) $latestVersion->is_force_update; $this->result['is_compatible'] = (bool) $latestVersion->is_compatible; $this->result['update_type'] = $latestVersion->update_type; $this->result['download_url'] = $latestVersion->download_url; $this->result['update_content'] = $latestVersion->update_content; $this->result['size'] = $latestVersion->size; // 生成提示信息 $this->generateMessage($latestVersion, $currentVersionCode); } else { $this->result['message'] = '当前已是最新版本'; } // 检查是否低于最低支持版本 if ($currentVersionCode < $latestVersion->min_support_version) { $this->result['need_update'] = true; $this->result['is_force_update'] = true; $this->result['is_compatible'] = false; $this->result['update_type'] = 'required'; $this->result['message'] = '当前版本过低,必须升级到最新版本才能继续使用'; } return $this->result; } /** * 判断是否需要跳过版本检查 */ protected function shouldExcept(): bool { $path = Request::path(); foreach ($this->except as $except) { if ($except === $path || fnmatch($except, $path)) { return true; } } return false; } /** * 判断是否需要强制版本检查 */ protected function isForceCheck(): bool { $path = Request::controller() . '/' . Request::action(); foreach ($this->forceCheck as $check) { if ($check === $path || fnmatch($check, $path)) { return true; } } return false; } /** * 记录请求信息 */ protected function logRequestInfo(string $platform, int $versionCode, string $versionName, string $channel): void { // 只记录特定接口的日志(避免日志过多) $logPaths = [ 'api/user/*', 'api/order/*', 'api/payment/*', ]; $path = Request::path(); foreach ($logPaths as $logPath) { if (fnmatch($logPath, $path)) { Log::info('APP请求', [ 'path' => $path, 'method' => Request::method(), 'platform' => $platform, 'version_code' => $versionCode, 'version_name' => $versionName, 'channel' => $channel, 'ip' => Request::ip(), 'user_agent' => Request::header('Member-Agent', ''), ]); break; } } } /** * 错误响应 */ protected function errorResponse(int $code, string $message, array $data = []): Response { return json([ 'code' => $code, 'message' => $message, 'data' => $data, 'timestamp' => time(), ], $code); } /** * 生成提示信息 * @param AppVersion $latestVersion 最新版本 * @param int $currentVersionCode 当前版本号 */ protected function generateMessage(AppVersion $latestVersion, int $currentVersionCode): void { $versionName = $latestVersion->version_name; if ($latestVersion->is_force_update) { $this->result['message'] = "发现新版本 {$versionName},请立即升级"; } elseif ($latestVersion->update_type === 'required') { $this->result['message'] = "发现重要更新 {$versionName},建议立即升级"; } elseif ($latestVersion->update_type === 'recommended') { $this->result['message'] = "发现新版本 {$versionName},建议升级"; } else { $this->result['message'] = "发现新版本 {$versionName},可选择升级"; } // 如果不兼容,添加警告 if (!$latestVersion->is_compatible) { $this->result['message'] .= "(当前版本将无法继续使用)"; } } /** * 版本号转换为版本名称 * @param int $versionCode 版本号 101 * @return string 版本名称 1.0.1 */ public static function versionCodeToName(int $versionCode): string { $major = floor($versionCode / 100); $minor = floor(($versionCode % 100) / 10); $patch = $versionCode % 10; return "{$major}.{$minor}.{$patch}"; } /** * 版本名称转换为版本号 * @param string $versionName 版本名称 1.0.1 * @return int 版本号 101 */ public static function versionNameToCode(string $versionName): int { $parts = explode('.', $versionName); $parts = array_pad($parts, 3, 0); return (int)($parts[0] * 100 + $parts[1] * 10 + $parts[2]); } }