host(); // 获取当前请求路径 $path = $request->pathinfo(); // 白名单路径检查 - 不需要验证域名的路径 $whitelistPaths = $this->getWhitelistPaths(); foreach ($whitelistPaths as $whitePath) { // 支持简单的路径匹配,如 'notify/*' 匹配所有通知路径 if ($this->pathMatch($whitePath, $path)) { // 白名单路径,跳过域名检查 \think\facade\Log::info('白名单路径访问: ' . $path . ', 域名: ' . $domain); return $next($request); } } // 检查域名是否允许 if (!$this->checkDomain($domain)) { // 记录被拒绝的请求 \think\facade\Log::warning('域名访问被拒绝: ' . $domain . ', 路径: ' . $path); return $this->error('未授权,无法访问', 403); } // 域名允许,记录请求信息(可选,仅用于调试) // \think\facade\Log::info('域名访问通过: ' . $domain . ', 路径: ' . $path); // 域名允许,继续处理请求 return $next($request); } /** * 检查域名是否在允许列表中 * * @param string $domain 请求域名 * @return bool 是否允许 */ protected function checkDomain($domain) { // 获取所有允许的域名 $allowedDomains = $this->getAllowedDomains(); // 如果没有设置允许的域名,默认放行所有域名 if (empty($allowedDomains)) { return true; } // 检查域名是否在允许列表中 foreach ($allowedDomains as $allowedDomain) { // 支持通配符匹配,例如 *.example.com if ($this->domainMatch($allowedDomain, $domain)) { return true; } } // 域名不在允许列表中 return false; } /** * 获取所有允许的域名 * * @return array 允许的域名列表 */ protected function getAllowedDomains() { $allowedDomains = []; // 1. 从配置文件中获取额外允许的域名 try { $configDomains = Config::get('api.admin_domains', []); if (!empty($configDomains) && is_array($configDomains)) { foreach ($configDomains as $d) { $d = trim($d); if (!empty($d) && !in_array($d, $allowedDomains)) { $allowedDomains[] = $d; } } } } catch (\Exception $e) { // 配置获取失败,记录日志 \think\facade\Log::error('获取API域名配置失败: ' . $e->getMessage()); } // 3. 记录可用域名到日志,用于调试 // \think\facade\Log::info('允许的域名列表: ' . json_encode($allowedDomains)); return $allowedDomains; } /** * 检查域名是否匹配 * * @param string $pattern 配置中的域名模式 * @param string $domain 当前请求的域名 * @return bool 是否匹配 */ protected function domainMatch($pattern, $domain) { // 完全匹配 if (strtolower($pattern) === strtolower($domain)) { return true; } // 通配符匹配 (例如: *.example.com) if (strpos($pattern, '*') !== false) { $pattern = str_replace('*', '.*', $pattern); $pattern = '/^' . str_replace('.', '\.', $pattern) . '$/i'; return preg_match($pattern, $domain); } return false; } /** * 返回错误信息 * * @param string $msg 错误信息 * @param int $code 错误码 * @return \think\Response */ protected function error($msg, $code = 0) { $result = [ 'status' => $code, 'msg' => $msg, 'data' => null ]; $response = Response::create($result, 'json', $code); throw new HttpResponseException($response); } /** * 中间件结束调度 * * @param \think\Response $response * @return void */ public function end(\think\Response $response) { // 可以在响应发送前进行一些后置处理 } /** * 获取路径白名单 * * @return array 白名单路径列表 */ protected function getWhitelistPaths() { // 1. 默认白名单路径(如支付回调通知等) $defaultWhitelist = [ 'notify/*', // 支付回调等通知 'health', // 健康检查 'debug', // 调试接口 ]; // 2. 从配置文件中获取白名单路径 try { $configWhitelist = Config::get('api.whitelist_paths', []); if (!empty($configWhitelist) && is_array($configWhitelist)) { return array_merge($defaultWhitelist, $configWhitelist); } } catch (\Exception $e) { \think\facade\Log::error('获取API白名单路径配置失败: ' . $e->getMessage()); } return $defaultWhitelist; } /** * 路径匹配检查 * * @param string $pattern 白名单路径模式 * @param string $path 请求路径 * @return bool 是否匹配 */ protected function pathMatch($pattern, $path) { // 完全匹配 if ($pattern === $path) { return true; } // 通配符匹配 (例如: 'notify/*') if (strpos($pattern, '*') !== false) { $pattern = str_replace('*', '.*', $pattern); $pattern = '/^' . str_replace('/', '\/', $pattern) . '$/i'; return preg_match($pattern, $path); } return false; } }