先说明新建的三个文件都是干嘛的
GlobalData.php 使用了 GlobalData变量共享组件
HealthCheck.php 按照设定的时间做健康检查
Proxy.php 代理设置,需要将后台的代理改成这里的设置
使用Composer添加一些依赖
composer require workerman/globaldata
composer require guzzlehttp/guzzle
在 process
目录新建 GlobalData.php
并添加代码
<?php
namespace process;
class GlobalData extends \GlobalData\Server
{
}
process
目录新建 HealthCheck.php
并添加代码<?php
namespace process;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Promise\Utils;
use Psr\Http\Message\ResponseInterface;
use Workerman\Worker;
class HealthCheck
{
//没五分钟做一次健康检查
protected bool $quitSignal = false;
//这里填你需要监控的代理列表
protected array $list = [
['protocol' => 'https', 'domain' => 'api.xxx.ink'],
['protocol' => 'https', 'domain' => 'api.xxxx.fun'],
];
protected \GlobalData\Client $GlobalData;
protected string $GlobalKey = 'serviceList';
public function onWorkerStart(Worker $worker)
{
$this->GlobalData = new \GlobalData\Client('127.0.0.1:2207');
$this->quitSignal = false;
$this->check();
}
public function onWorkerStop(Worker $worker)
{
$this->quitSignal = true;
}
public function check(): void
{
$client = new Client([
\GuzzleHttp\RequestOptions::CONNECT_TIMEOUT => 30,
\GuzzleHttp\RequestOptions::TIMEOUT => 30,
\GuzzleHttp\RequestOptions::VERIFY => false,
\GuzzleHttp\RequestOptions::HTTP_ERRORS => false,
]);
$promises = [];
foreach ($this->list as $item) {
$now = intval(microtime(true) * 1000);
$protocol = $item['protocol'];
$domain = $item['domain'];
try {
$client->get("{$protocol}://$domain");
$newTime = intval(microtime(true) * 1000) - $now;
//echo "请求域名:{$domain} 响应:{$newTime}ms\n";
$this->setKeyGlobalData($domain, $newTime);
} catch (GuzzleException $e) {
//echo "请求{$domain}失败:{$e->getMessage()}\n";
if (!empty($this->GlobalData->serviceList) && isset($this->GlobalData->serviceList[$domain])) {
//echo "域名下线:{$domain}\n";
$this->delKeyGlobalData($domain);
}
}
}
if (!$this->quitSignal){
\Workerman\Timer::add(300, [$this, 'check'], [], false);
}
}
protected function setKeyGlobalData($key, $value): void
{
$serviceList = $this->GlobalData->serviceList;
if (empty($serviceList)) $serviceList = [];
$serviceList[$key] = $value;
$this->GlobalData->serviceList = $serviceList;
}
protected function delKeyGlobalData($key): void
{
$serviceList = $this->GlobalData->serviceList;
if (empty($serviceList)) $serviceList = [];
if (isset($serviceList[$key])){
unset($serviceList[$key]);
}
$this->GlobalData->serviceList = $serviceList;
}
}
process
目录新建 Proxy.php
并添加代码
<?php
namespace process;
use Workerman\Connection\AsyncTcpConnection;
use Workerman\Connection\TcpConnection;
use Workerman\Protocols\Http\Request;
use Workerman\Worker;
class Proxy
{
//这里填和HealthCheck.php配置一样的代理列表
protected array $defaultHostList = [
'api.xxx.ink',
'api.xxx.fun'
];
protected $selectChannel = [];
//这里填非官方的代理地址和key,我用了一个第三方的GTP4的代理(密钥=》代理地址)
protected array $tokenHostList = [
'sk-xxxxxxxxxxxxx' => ['api.xxx.ltd'],
];
protected \GlobalData\Client $GlobalData;
public function onWorkerStart(Worker $worker): void
{
$this->GlobalData = new \GlobalData\Client('127.0.0.1:2207');
}
public function onMessage(TcpConnection $connection, Request $request): void
{
$this->cluster($connection, $request);
}
/**
* 集群调用
* @param TcpConnection $connection
* @param Request $request
* @return void
*/
public function cluster(TcpConnection $connection, Request $request): void
{
$authorization = $request->header('Authorization','');
list(, $token) = explode(' ', $authorization);
$hostList = $this->defaultHostList;
if (isset($this->tokenHostList[$token])){
$hostList = $this->tokenHostList[$token];
}
$hostList = $this->getHostList($hostList);
foreach ($hostList as $host) {
try {
$this->proxy($connection, $request, $host);
$this->selectChannel[$host] = true;
return;
} catch (\Exception $e) {
continue;
}
}
//如果全部都失败则直接返回超时提示
$connection->send('{"error":{"code":503,"message":"Proxy Service Timeout.","param":null,"type":"cf_service_unavailable"}}');
}
/**
* @param $hostList
* @return string[]
*/
public function getHostList($hostList): array
{
$serviceList = $this->GlobalData->serviceList;
$list_a = [];
$list_b = [];
foreach ($hostList as $domain) {
if (isset($serviceList[$domain])){
if (isset($this->selectChannel[$domain])){
$serviceList[$domain] = $serviceList[$domain] - 120;
}
$list_a[$domain] = $serviceList[$domain];
}else{
$list_b[] = $domain;
}
}
$list_a = array_flip($list_a);
ksort($list_a);
$retList = [];
foreach ($list_a as $domain) {
$retList[] = $domain;
}
foreach ($list_b as $domain) {
$retList[] = $domain;
}
return $retList;
}
/**
* @throws \Exception
*/
public function proxy(TcpConnection $connection, Request $request, string $host): void
{
$buffer = (string)$request;
$con = new AsyncTcpConnection("tcp://$host:443", ['ssl' =>['verify_peer' => false]]);
$buffer = preg_replace("/Host: ?(.*?)\r\n/", "Host: $host\r\n", $buffer);
$con->transport = 'ssl';
$connection->protocol = null;
$con->send($buffer);
$con->pipe($connection);
$connection->pipe($con);
$con->connect();
}
}
在config/process.php
里面增加下面的配置
'Proxy' => [
'handler' => process\Proxy::class,
'listen' => 'http://0.0.0.0:8988',//本地代理地址,可以自己更换
'count' => cpu_count(),
'reloadable' => false,
],
'GlobalData' => [
'handler' => process\GlobalData::class,
'listen' => 'frame://127.0.0.1:2207',
'count' => 1,
'constructor' => [
'ip' => '127.0.0.1',
'port' => 2207
],
],
'HealthCheck' => [
'handler' => process\HealthCheck::class,
'count' => 1,
],
赞