添加后台代理代码

This commit is contained in:
2026-03-15 01:27:05 +08:00
parent 11f9ac4dc1
commit ea08c9366a
5254 changed files with 721042 additions and 0 deletions

View File

@@ -0,0 +1,346 @@
<?php
/**
* $Id: Router.php 58820 2015-01-16 16:29:33Z caoyangmin $
* @author caoyangmin(caoyangmin@baidu.com)
* @brief Router
*/
namespace phprs;
use phprs\util\HttpRouterEntries;
use phprs\util\Verify;
use phprs\util\AutoClassLoader;
use phprs\util\Logger;
use phprs\util\exceptions\NotFound;
use phprs\util\ClassLoader;
/**
* 结果保留在内存
* @author caoym
*/
class BufferedRespond extends Response{
/**
* flush 不直接输出
* @see \phprs\Response::flush()
* @param string limit 现在输出的项目
* @param callable $func 输出方法
* @return void
*/
public function flush($limit=null, $func=null)
{
return ;
}
}
/**
* restful api 路由
* 加载API类文件, 通过API类的@annotation获取路由规则信息, 当请求到来时, 调用匹配的
* API类方法.
*
* 通常每一个请求只对应到一个最严格匹配的API接口, 所谓"最严格匹配",比如:
* API1 接口 => url: /apis/test
* API2 接口 => url: /apis
* 那么当请求"/apis/test/123/" 最严格匹配的接口是API1
*
* 如果需要让一个请求经过多个API调用, 比如有时候会需要一个统一验证的接口, 让所有请
* 求先通过验证接口, 再调用其他接口. 此时可以通过Router的hooks属性, 设置一组hook实
* 现. hook其实和普通的接口一样, 只是在hooks中指定后, 执行行为将有所不同: 请求会按
* 优先级逐个经过hook, 只要匹配, hook的方法就会被调用, 直到最后调用普通的API
*
* 通过@return({"break", true})停止请求链路
*
* @author caoym
*/
class Router
{
private $class_loader; //用于确保反序列化时自动加载类文件
/**
*
*/
function __construct( ){
//AutoClassLoader确保序列化后自动加载类文件
$this->class_loader = new AutoClassLoader();
ClassLoader::addInclude($this->api_path);
//TODO: 支持名字空间
//TODO: 支持多路径多类名
$this->load($this->api_path, $this->apis, $this->api_method);
//允许通过接口访问api信息
if($this->export_apis){
$this->loadApi($this->routes, __DIR__.'/apis/ApiExporter.php', 'phprs\apis\ApiExporter');
}
}
/**
* 获取api文件列表
* @return array
*/
public function getApiFiles(){
return $this->class_loader->getClasses();
}
/**
* 调用路由规则匹配的api
* @param Request $request
* @param Response $respond
* @return void
*/
public function __invoke($request=null, &$respond=null){
if($request === null){
$request = new Request(null,$this->url_begin);
}
if($respond==null){
$respond = new Response();
}
$request['$.router'] = $this;
//先按配置的顺序调用hook
foreach ($this->hook_routes as $hook){
$res = new BufferedRespond();
if(!$this->invokeRoute($hook, $request, $res)){
continue;
}
$respond->append($res->getBuffer());
$break = false;
$respond->flush('break', function($var)use(&$break){$break = $var;});
if($break){
Logger::info("invoke break");
$respond->flush();
return;
}
}
$res = new BufferedRespond();
Verify::isTrue($this->invokeRoute($this->routes, $request, $res), new NotFound());
$respond->append($res->getBuffer());
$respond->flush();
}
/**
*
* @param string|array $api_path
* @param string $apis
* @param string $api_method
*/
public function load($api_path, $apis=null , $api_method=null){
if(is_string($api_path)){
$api_paths = array($api_path);
}else{
$api_paths = $api_path;
}
Verify::isTrue(is_array($api_paths), 'invalid param');
foreach ($api_paths as $api_path){
$this->loadRoutes($this->routes, $api_path, $apis, $api_method);
foreach ($this->hooks as $hook) {
$hook_route=array();
$this->loadRoutes($hook_route, $api_path.'/hooks', $hook, null);
$this->hook_routes[] = $hook_route;
}
}
}
/**
* @return array
*/
public function getRoutes(){
return $this->routes;
}
/**
* @return array
*/
public function getHooks(){
return $this->hook_routes;
}
/**
* 调用路由规则匹配的api
* @param array $routes 路由规则
* @param unknown $request
* @param unknown $respond
* @return boolean 是否有匹配的接口被调用
*/
private function invokeRoute($routes, $request, &$respond){
$method = $request['$._SERVER.REQUEST_METHOD'];
$path = $request['$.path'];
$uri = $request['$._SERVER.REQUEST_URI'];
list(,$params) = explode('?', $uri)+array( null,null );
$params = is_null($params)?null:explode('&', $params);
Logger::debug("try to find route $method ".$uri);
$match_path = array();
if(isset($routes[$method])){
if(($api = $routes[$method]->findByArray($path,$params,$match_path)) !== null){
Logger::debug("invoke $uri => {$api->getClassName()}::{$api->getMethodName()}");
$api($request, $respond);
return true;
}
}
if(!isset($routes['*'])){
return false;
}
if(($api = $routes['*']->find($uri, $match_path)) === null){
return false;
}
Logger::debug("invoke $uri => {$api->getClassName()}::{$api->getMethodName()}");
$api($request, $respond);
return true;
}
/**
* @param string $apis_dir
* @param string $class
* @param string $method
* @return void
*/
public function addRoutes($apis_dir, $class=null, $method=null){
$this->loadRoutes($this->routes, $apis_dir, $class, $method);
}
/**
* 遍历API目录生成路由规则
* @param object $factory
* @param string $apis_dir
* @param string $class
* @param string $method
* @param array $skipclass 需要跳过的类名
* @return Router
*/
private function loadRoutes(&$routes, $apis_dir, $class, $method){
Logger::info("attempt to load router $apis_dir");
$dir = null;
if(is_dir($apis_dir) && $class === null){
$apis_dir=$apis_dir.'/';
Verify::isTrue(is_dir($apis_dir), "$apis_dir not a dir");
$dir = @dir($apis_dir);
Verify::isTrue($dir !== null, "open dir $apis_dir failed");
$geteach = function ()use($dir){
$name = $dir->read();
if(!$name){
return $name;
}
return $name;
};
}else{
if(is_file($apis_dir)){
$files = array($apis_dir);
$apis_dir = '';
}else{
$apis_dir=$apis_dir.'/';
if(is_array($class)){
foreach ($class as &$v){
$v .= '.php';
}
$files = $class;
}else{
$files = array($class.'.php');
}
}
$geteach = function ()use(&$files){
// 🚨 PHP8兼容性each()函数替换,严格保持原有行为
// ⚠️ 重要必须保持数组指针移动行为与each()完全一致
// ⚠️ 重要:必须保持文件加载顺序绝对不变
$key = key($files);
if($key === null) {
return false; // ⚠️ 保持与each()相同的结束判断逻辑
}
$value = current($files);
next($files); // ⚠️ 关键保持指针移动与each()完全一致
return $value; // ⚠️ 保持返回值格式与each()[1]完全一致
};
}
while( !!($entry = $geteach()) ){
$path = $apis_dir. str_replace('\\', '/', $entry);
if(is_file($path) && substr_compare ($entry, '.php', strlen($entry)-4,4,true) ==0){
$class_name = substr($entry, 0, strlen($entry)-4);
$this->loadApi($routes, $path, $class_name, $method);
}else{
Logger::debug($path.' ignored');
}
}
if($dir !== null){
$dir->close();
}
Logger::info("load router $apis_dir ok");
return $routes;
}
/**
* 加载api类
* @param array $routes
* @param string $class_file
* @param string $class_name
* @param string $method
* @return void
*/
private function loadApi(&$routes, $class_file, $class_name, $method=null){
Verify::isTrue(is_file($class_file), $class_file.' is not an exist file');
Logger::debug("attempt to load api: $class_name, $class_file");
$this->class_loader->addClass($class_name, $class_file);
$api = null;
if ($this->ignore_load_error){
try {
$api = $this->factory->create('phprs\\Container', array($class_name, $method), null, null);
} catch (\Exception $e) {
Logger::warning("load api: $class_name, $class_file failed with ".$e->getMessage());
return ;
}
}else{
$api = $this->factory->create('phprs\\Container', array($class_name, $method), null, null);
}
foreach ($api->routes as $http_method=>$route){
if(!isset($routes[$http_method])){
$routes[$http_method] = new HttpRouterEntries();
}
$cur = $routes[$http_method];
foreach ($route as $entry){
list($uri,$invoke,$strict) = $entry;
$realpath = preg_replace('/\/+/', '/', '/'.$uri);
$strict = ($strict===null)?$this->default_strict_matching:$strict;
Verify::isTrue($cur->insert($realpath, $invoke, $strict), "repeated path $realpath");
Logger::debug("api: $http_method $realpath => $class_name::{$entry[1]->method_name} ok, strict:$strict");
}
}
Logger::debug("load api: $class_name, $class_file ok");
}
private $routes=array();
private $hook_routes=array();
/** @inject("ioc_factory") */
public $factory;
/** @property */
private $api_path='apis';
/** @property */
private $apis=null;
/** @property */
private $api_method=null;
private $api_root=null;
/** @property
* 指定hook的类名, 从优先级高到低
* 如果一条规则匹配多个hook, 则优先级高的先执行, 再执行优先级低的
*/
private $hooks=array();
/**
* @property
* 是否允许通过接口获取api信息
*/
private $export_apis=true;
/**
* 用于匹配路由的url偏移
* @property
*/
public $url_begin=0;
/**
* 指定路由规则默认情况下是否严格匹配path如果@route中已经指定严格模式则忽略此默认设置
* 严格模式将只允许同级目录匹配,否则父目录和子目录也匹配。
* 非严格匹配时
* 路由"GET /a" 和请求"GET /a"、"GET /a/b"、"GET /a/b/c"等匹配
* 严格匹配时
* 路由"GET /a" 和请求"GET /a"匹配、和"GET /a/b"、"GET /a/b/c"等不匹配
* @property
*/
public $default_strict_matching=false;
/**
* @property 忽略类加载时的错误,只是跳过出错的接口。否则抛出异常。
*/
public $ignore_load_error=true;
}