first commit
This commit is contained in:
commit
08da50a0b7
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
/.idea/
|
||||
/vendor/
|
||||
/test/
|
||||
|
||||
composer.lock
|
3
README.md
Normal file
3
README.md
Normal file
@ -0,0 +1,3 @@
|
||||
```shell
|
||||
php artisan vendor:publish --provider=StarPoolCloud\Providers\StarPoolCloudServiceProvider
|
||||
```
|
39
composer.json
Normal file
39
composer.json
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "starpoolcloud/base-php",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "lifangyi",
|
||||
"email": "lifangyi@squrab.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^8.0",
|
||||
"ext-openssl": "*",
|
||||
"ext-curl": "*",
|
||||
"ext-bcmath": "*",
|
||||
"laravel/framework": "^8.40",
|
||||
"ramsey/uuid": "^4.1",
|
||||
"guzzlehttp/guzzle": "^7.3",
|
||||
"zoujingli/ip2region": "^1.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"StarPoolCloud\\": "src/"
|
||||
},
|
||||
"files": [
|
||||
"src/Handles/File/helper.php",
|
||||
"src/Handles/File/code.php"
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"StarPoolCloud\\Providers\\StarPoolCloudServiceProvider"
|
||||
],
|
||||
"aliases": {
|
||||
"StarPoolCloud": "StarPoolCloud\\Facades\\StarPoolCloudFacade"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
10
config/apollo.php
Normal file
10
config/apollo.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'server_addr' => 'https://apollo.config.squrab.com',
|
||||
'cluster' => 'dev',
|
||||
'client_ip' => '127.0.0.1',
|
||||
'appid' => env('APP_NAME', ''),
|
||||
'secret' => env('APOLLO_SECRET', ''),
|
||||
'namespace' => env('APP_ENV', '')
|
||||
];
|
47
src/Commands/ApolloConfigSync.php
Normal file
47
src/Commands/ApolloConfigSync.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace StarPoolCloud\Commands;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ApolloConfigSync extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'apollo:sync';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = '同步Apollo配置';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
app('StarPoolCloudApollo')->updateEnv();
|
||||
$this->info('Success');
|
||||
} catch (Exception $exception) {
|
||||
$this->error("Error:{$exception->getMessage()}");
|
||||
}
|
||||
}
|
||||
}
|
24
src/Facades/StarPoolCloudFacade.php
Normal file
24
src/Facades/StarPoolCloudFacade.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace StarPoolCloud\Facades;
|
||||
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
/**
|
||||
* @method static SignatureRequestHttpPostByAes(string $url, $productID, string $key, string $vi, array $params): string|false
|
||||
* @method static AesEncrypt(string $data, string $key, string $vi): string|false
|
||||
* @method static AesDecrypt(string $data, string $key, string $vi): string|false
|
||||
* @method static Base64UrlEncode(string $data, bool $strict = false): string
|
||||
* @method static Base64UrlDecode(string $data, bool $strict = false): string
|
||||
* @method static DingTalkAlert(string $accessToken, string $secret, array $params): bool
|
||||
* @method static PostJsonRequest(string $url, array $params, array $header = []): string
|
||||
* @method static YouDaoTextTranslator(string $appID, string $secret, string $text, string $lang): string
|
||||
*/
|
||||
class StarPoolCloudFacade extends Facade
|
||||
{
|
||||
protected static function getFacadeAccessor(): string
|
||||
{
|
||||
return 'StarPoolCloud';
|
||||
}
|
||||
}
|
460
src/Handles/Apollo/Client.php
Normal file
460
src/Handles/Apollo/Client.php
Normal file
@ -0,0 +1,460 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace StarPoolCloud\Handles\Apollo;
|
||||
|
||||
use Dotenv\Dotenv;
|
||||
use Exception;
|
||||
use GuzzleHttp\Promise\PromiseInterface;
|
||||
use GuzzleHttp\Client as GuzzleHttpClient;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
|
||||
class Client
|
||||
{
|
||||
protected mixed $configServerUrl = '';
|
||||
protected string $clientIp = '';
|
||||
protected string $appId = '';
|
||||
protected string $namespaceName = '';
|
||||
protected string $clusterName = '';
|
||||
protected string $secret = '';
|
||||
|
||||
protected array $httpInfo;
|
||||
protected array $errorInfo;
|
||||
protected array $onRejectedTimeList;
|
||||
protected GuzzleHttpClient $guzzleHttpClient;
|
||||
protected PromiseInterface $promise;
|
||||
|
||||
protected bool $isMultiGet = false;//是否批量获取配置
|
||||
protected bool $promiseWait = true;//是否启用promise wait
|
||||
protected mixed $asyncGetResult = null;//通过回调函数异步获取返回结果
|
||||
protected bool $setHttpInfo = true;//附带http信息
|
||||
protected bool $setErrorInfo = true;//附带错误信息
|
||||
|
||||
/**
|
||||
* 创建配置
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct(array $config)
|
||||
{
|
||||
foreach ($config as $key => $item) {
|
||||
if (blank($item)) {
|
||||
throw new Exception("必须配置$key", -1);
|
||||
}
|
||||
}
|
||||
$this->configServerUrl = $config['server_addr'];
|
||||
$this->clientIp = $config['client_ip'];
|
||||
$this->clusterName = $config['cluster'];
|
||||
$this->namespaceName = $config['namespace'];
|
||||
$this->secret = $config['secret'];
|
||||
$this->appId = $config['appid'];
|
||||
$this->guzzleHttpClient = new GuzzleHttpClient(['http_errors' => false]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从阿波罗服务器读取配置
|
||||
* @param bool $useCacheApi 是否通过带缓存的Http接口从Apollo读取配置,设置为false可以使用不带缓存的Http接口从Apollo读取配置
|
||||
* @param string $releaseKey 上一次的releaseKey
|
||||
* @return array
|
||||
*/
|
||||
public function getConfig(bool $useCacheApi = false, string $releaseKey = ''): array
|
||||
{
|
||||
$this->isMultiGet = false;
|
||||
$res = $this->multiGetConfig([$this->appId => [$this->namespaceName => $releaseKey]], $useCacheApi);
|
||||
//当前应用指定namespace的http请求信息
|
||||
if (isset($this->httpInfo[$this->appId][$this->namespaceName])) {
|
||||
$this->httpInfo = $this->httpInfo[$this->appId][$this->namespaceName];
|
||||
} else {
|
||||
$this->httpInfo = [];
|
||||
}
|
||||
//当前应用指定namespace的错误信息
|
||||
if (isset($this->errorInfo[$this->appId][$this->namespaceName])) {
|
||||
$this->errorInfo = $this->errorInfo[$this->appId][$this->namespaceName];
|
||||
} else {
|
||||
$this->errorInfo = [];
|
||||
}
|
||||
if (!empty($this->errorInfo)) {
|
||||
return [];
|
||||
}
|
||||
return $res[$this->appId][$this->namespaceName] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新.env文件
|
||||
* @throws Exception
|
||||
*/
|
||||
public function updateEnv()
|
||||
{
|
||||
$config = $this->getConfig();
|
||||
if (blank($config)) {
|
||||
throw new Exception('未获取Apollo配置', -1);
|
||||
}
|
||||
$envFile = base_path(app()->environmentFile());
|
||||
$envFileContent = file_get_contents($envFile);
|
||||
if ($envFileContent === false) {
|
||||
throw new Exception('无法读取.env文件', -1);
|
||||
}
|
||||
$envArr = Dotenv::parse($envFileContent);
|
||||
$toArray = collect($envArr)->merge($config)->sortKeys()->toArray();
|
||||
$newEnvFileContent = '';
|
||||
$group = [];
|
||||
$i = 0;
|
||||
foreach ($toArray as $name => $value) {
|
||||
$prefix = '';
|
||||
$explode = explode('_', $name);
|
||||
$group[$i] = $explode[0];
|
||||
if ($i > 0 && $group[$i - 1] !== $group[$i]) {
|
||||
$prefix = "\n";
|
||||
}
|
||||
$newEnvFileContent .= sprintf("%s%s=%s%s", $prefix,$name, $value, "\n");
|
||||
$i++;
|
||||
}
|
||||
file_put_contents($envFile, $newEnvFileContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量读取配置
|
||||
* @param array $appNamespaceData 应用id及其Namespace列表信息,格式例子:
|
||||
* Array(
|
||||
* 'app_id_1' => [
|
||||
* 'application' => '',
|
||||
* 'FX.apollo' => ''
|
||||
* ],
|
||||
* 'app_id_2' => [
|
||||
* 'application' => ''
|
||||
* ]
|
||||
* )
|
||||
* @param bool $useCacheApi 是否通过带缓存的Http接口从Apollo读取配置,设置为false可以使用不带缓存的Http接口从Apollo读取配置
|
||||
* @return array
|
||||
*/
|
||||
public function multiGetConfig(array $appNamespaceData, bool $useCacheApi = true): array
|
||||
{
|
||||
$this->httpInfo = [];
|
||||
|
||||
$res = [];
|
||||
if (empty($appNamespaceData)) {
|
||||
return $res;
|
||||
}
|
||||
|
||||
$isMultiGet = $this->isMultiGet;//是否批量获取配置
|
||||
$asyncGetResult = $this->asyncGetResult;//通过回调函数异步获取返回结果
|
||||
$setHttpInfo = $this->setHttpInfo;//附带http信息
|
||||
$setErrorInfo = $this->setHttpInfo;//附带错误信息
|
||||
|
||||
foreach ($appNamespaceData as $appId => &$namespaceData) {
|
||||
foreach ($namespaceData as $namespaceName => &$releaseKey) {
|
||||
//带缓存接口,置空releaseKey
|
||||
$useCacheApi === true && $releaseKey = '';
|
||||
//初始化返回结果
|
||||
!isset($res[$appId][$namespaceName]) && $res[$appId][$namespaceName] = [];
|
||||
$this->promise = $this->requestAsync(
|
||||
$this->buildGetConfigRequestUrl($appId, $namespaceName, $useCacheApi, $releaseKey),
|
||||
$appId,
|
||||
['timeout' => 10]//默认10秒超时
|
||||
);
|
||||
$this->promise->then(
|
||||
function (ResponseInterface $response) use (
|
||||
&$res,
|
||||
$appId,
|
||||
$namespaceName,
|
||||
$useCacheApi,
|
||||
$asyncGetResult,
|
||||
$isMultiGet,
|
||||
$setHttpInfo
|
||||
) {
|
||||
$responseCode = $response->getStatusCode();
|
||||
$responseBody = (string)$response->getBody();
|
||||
if ($setHttpInfo === true) {
|
||||
$this->httpInfo[$appId][$namespaceName] = [
|
||||
'response_code' => $responseCode,
|
||||
'response_body' => $responseBody
|
||||
];
|
||||
}
|
||||
switch ($responseCode) {
|
||||
case 200:
|
||||
$responseBody = json_decode($responseBody, true);
|
||||
empty($responseBody) && $responseBody = [];
|
||||
break;
|
||||
case 304:
|
||||
$responseBody = [];
|
||||
break;
|
||||
default:
|
||||
$responseBody = false;
|
||||
}
|
||||
if ($useCacheApi === false) {//不带缓存的接口,配置项在configurations里面
|
||||
$res[$appId][$namespaceName] = $responseBody['configurations'];
|
||||
} else {//带缓存的接口,responseBody就是配置项
|
||||
$res[$appId][$namespaceName] = $responseBody;
|
||||
}
|
||||
//把结果通过$asyncGetResult回调函数交给上层
|
||||
if (is_callable($asyncGetResult)) {
|
||||
call_user_func($asyncGetResult, $isMultiGet === true ? $res : $res[$appId][$namespaceName]);
|
||||
}
|
||||
},
|
||||
function (RequestException $exception) use (&$res, $appId, $namespaceName, $setErrorInfo) {
|
||||
if ($setErrorInfo === true) {
|
||||
$this->errorInfo[$appId][$namespaceName] = [
|
||||
'code' => $exception->getCode(),
|
||||
'message' => $exception->getMessage()
|
||||
];
|
||||
}
|
||||
$res[$appId][$namespaceName] = false;//存在异常则设置结果为false
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$this->promiseWait === true && $this->promiseWait();
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 多个应用感知配置更新
|
||||
* @param array $appNotificationsData 应用id及notifications信息,格式例子:
|
||||
* Array(
|
||||
* 'app_id_1' => [
|
||||
* 'application' => 100,
|
||||
* 'FX.apollo' => 200
|
||||
* ],
|
||||
* 'app_id_2' => [
|
||||
* 'application' => 100
|
||||
* ]
|
||||
* )
|
||||
* @param mixed|null $onConfigUpdate 当存在配置更新时触发的回调函数
|
||||
* @param null $onResponse
|
||||
* @return void
|
||||
*/
|
||||
public function listenMultiAppConfigUpdate(array $appNotificationsData, mixed $onConfigUpdate = null, $onResponse = null): void
|
||||
{
|
||||
if (empty($appNotificationsData)) {
|
||||
return;
|
||||
}
|
||||
$this->httpInfo = [];
|
||||
|
||||
//以下是执行流程
|
||||
//发起http长轮询监听指定应用的配置更新(请求会被服务器hold住)
|
||||
//如果被监听namespace发生配置变更(服务器会立刻响应当前请求,返回新的notificationId)
|
||||
//本地拿到新的notificationId,更新本地的映射表,然后再次发起http长轮询监听指定应用的配置更新
|
||||
$loopForConfigUpdate = function ($appId, $namespaceNotificationMapping) use (
|
||||
&$onConfigUpdate, &$loopForConfigUpdate, &$onResponse
|
||||
) {
|
||||
//生成notifications
|
||||
$notifications = [];
|
||||
foreach ($namespaceNotificationMapping as $namespaceName => $notificationId) {
|
||||
$notifications[] = ['namespaceName' => $namespaceName, 'notificationId' => $notificationId];
|
||||
}
|
||||
$this->promise = $this->requestAsync(
|
||||
$this->buildAwareConfigUpdateUrl($appId, $notifications), $appId, ['timeout' => 63]
|
||||
);
|
||||
unset($notifications);
|
||||
|
||||
$this->promise->then(
|
||||
function (ResponseInterface $response) use (
|
||||
$appId, &$loopForConfigUpdate, &$namespaceNotificationMapping, &$onConfigUpdate, &$onResponse
|
||||
) {
|
||||
//触发响应函数
|
||||
if (is_callable($onResponse)) {
|
||||
call_user_func_array($onResponse, [$appId, $response]);
|
||||
}
|
||||
$responseCode = $response->getStatusCode();
|
||||
if ($responseCode === 200) {
|
||||
$body = $response->getBody();
|
||||
$body = json_decode($body, true);
|
||||
if (!empty($body) && is_array($body)) {
|
||||
foreach ($body as &$value) {
|
||||
if (
|
||||
!isset($value['namespaceName']) ||
|
||||
!isset($value['notificationId'])
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
$namespaceName = &$value['namespaceName'];
|
||||
$notificationId = &$value['notificationId'];
|
||||
if (
|
||||
isset($namespaceNotificationMapping[$namespaceName]) &&
|
||||
$namespaceNotificationMapping[$namespaceName] != $notificationId
|
||||
) {//配置发生变更了
|
||||
//更新映射表
|
||||
$namespaceNotificationMapping[$namespaceName] = $notificationId;
|
||||
//触发配置变更回调函数
|
||||
if (is_callable($onConfigUpdate)) {
|
||||
$this->promiseWait = false;//关闭getConfig方法内的promise wait
|
||||
$this->setHttpInfo = false;//关闭getConfig方法内的保存http信息的逻辑
|
||||
$this->setErrorInfo = false;//关闭getConfig方法内的保存错误信息的逻辑
|
||||
//由于接管了getConfig方法的promise wait,通过回调函数获取返回结果
|
||||
$this->asyncGetResult = function ($newConfig) use (
|
||||
$appId,
|
||||
$namespaceName,
|
||||
&$onConfigUpdate,
|
||||
$notificationId,
|
||||
&$namespaceNotificationMapping
|
||||
) {
|
||||
if ($newConfig !== false) {
|
||||
call_user_func_array(
|
||||
$onConfigUpdate,
|
||||
[
|
||||
$appId,
|
||||
$namespaceName,
|
||||
$newConfig,
|
||||
$notificationId,
|
||||
&$namespaceNotificationMapping
|
||||
]
|
||||
);
|
||||
}
|
||||
};
|
||||
//以下方法返回结果为空数组
|
||||
$this->getConfig(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//再次发起http长轮询监听指定应用的配置更新
|
||||
$loopForConfigUpdate($appId, $namespaceNotificationMapping);
|
||||
},
|
||||
function (RequestException $exception) use ($appId, &$loopForConfigUpdate, &$namespaceNotificationMapping) {//偶尔有些超时请求会从此处产生
|
||||
//防止因为阿波罗服务器异常而导致进入无限死循环
|
||||
$nowTime = time();
|
||||
$errorTimeLimit = 5;
|
||||
if (!empty($this->onRejectedTimeList[$appId])) {
|
||||
if (
|
||||
count($this->onRejectedTimeList[$appId]) === $errorTimeLimit &&
|
||||
count(array_unique($this->onRejectedTimeList[$appId])) <= 2
|
||||
) {//瞬间产生过多错误,退出event loop
|
||||
die('错误码:' . $exception->getCode() . ',错误信息:' . $exception->getMessage() . PHP_EOL);
|
||||
}
|
||||
}
|
||||
$this->onRejectedTimeList[$appId][] = $nowTime;
|
||||
if (count($this->onRejectedTimeList[$appId]) > $errorTimeLimit) {
|
||||
array_shift($this->onRejectedTimeList[$appId]);
|
||||
}
|
||||
//再次发起http长轮询监听指定应用的配置更新
|
||||
$loopForConfigUpdate($appId, $namespaceNotificationMapping);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
foreach ($appNotificationsData as $appId => $namespaceNotificationMapping) {
|
||||
if (empty($namespaceNotificationMapping)) {
|
||||
continue;
|
||||
}
|
||||
$loopForConfigUpdate($appId, $namespaceNotificationMapping);
|
||||
}
|
||||
|
||||
$this->promiseWait();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取http请求信息
|
||||
* @return array
|
||||
*/
|
||||
public function getHttpInfo(): array
|
||||
{
|
||||
return $this->httpInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误信息
|
||||
* @return array
|
||||
*/
|
||||
public function getErrorInfo(): array
|
||||
{
|
||||
return $this->errorInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建用于请求的阿波罗接口链接
|
||||
* @param string $appId 应用的appId
|
||||
* @param string $namespaceName Namespace的名字
|
||||
* @param bool $useCacheApi 是否通过带缓存的Http接口从Apollo读取配置
|
||||
* @param string $releaseKey 上一次的releaseKey
|
||||
* @return string
|
||||
*/
|
||||
private function buildGetConfigRequestUrl(string $appId, string $namespaceName, bool $useCacheApi = true, string $releaseKey = ''): string
|
||||
{
|
||||
if (empty($appId) || empty($namespaceName)) {
|
||||
return '';
|
||||
}
|
||||
if ($useCacheApi === true) {
|
||||
$url = "$this->configServerUrl/configfiles/json/$appId/$this->clusterName/$namespaceName";
|
||||
} else {
|
||||
$url = "$this->configServerUrl/configs/$appId/$this->clusterName/$namespaceName";
|
||||
}
|
||||
$params = [];
|
||||
if (!empty($this->clientIp)) {
|
||||
$params['ip'] = $this->clientIp;
|
||||
}
|
||||
if (!empty($releaseKey)) {
|
||||
$params['releaseKey'] = $releaseKey;
|
||||
}
|
||||
if (!empty($params)) {
|
||||
$url .= '?' . http_build_query($params);
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建用于请求的阿波罗接口链接
|
||||
* @param string $appId 应用的appId
|
||||
* @param array $notifications notifications信息,格式为二维数组,格式例子:
|
||||
* Array(
|
||||
* ['namespaceName' => 'application', 'notificationId' => 100],
|
||||
* ['namespaceName' => 'FX.apollo', 'notificationId' => 200]
|
||||
* )
|
||||
* @return string
|
||||
*/
|
||||
private function buildAwareConfigUpdateUrl(string $appId, array $notifications = []): string
|
||||
{
|
||||
if (empty($appId) || empty($notifications)) {
|
||||
return '';
|
||||
}
|
||||
$notifications = urlencode(json_encode($notifications));
|
||||
return "$this->configServerUrl/notifications/v2?appId=$appId&cluster=$this->clusterName¬ifications=$notifications";
|
||||
}
|
||||
|
||||
/**
|
||||
* 发起异步请求
|
||||
* @param string $url 请求链接
|
||||
* @param string $appId 应用的appId
|
||||
* @param array $options 请求配置,参考guzzlehttp文档
|
||||
* @return PromiseInterface
|
||||
*/
|
||||
private function requestAsync(string $url, string $appId = '', array $options = []): PromiseInterface
|
||||
{
|
||||
if (
|
||||
!empty($this->secret) &&
|
||||
!empty($appId)
|
||||
) {//追加访问密钥
|
||||
$timestamp = time() * 1000;
|
||||
$urlInfo = parse_url($url);
|
||||
if (!empty($urlInfo['path'])) {
|
||||
$pathWithQuery = $urlInfo['path'];
|
||||
if (!empty($urlInfo['query'])) {
|
||||
$pathWithQuery .= '?' . $urlInfo['query'];
|
||||
}
|
||||
$options['headers'][Signature::HTTP_HEADER_AUTHORIZATION] = Signature::getAuthorizationString(
|
||||
$appId, $timestamp, $pathWithQuery, $this->secret
|
||||
);
|
||||
$options['headers'][Signature::HTTP_HEADER_TIMESTAMP] = $timestamp;
|
||||
}
|
||||
unset($urlInfo);
|
||||
}
|
||||
return $this->guzzleHttpClient->requestAsync('GET', $url, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发起异步请求
|
||||
* @return void
|
||||
*/
|
||||
private function promiseWait(): void
|
||||
{
|
||||
if (!is_null($this->promise)) {
|
||||
try {
|
||||
$this->promise->wait();
|
||||
} catch (Exception) {
|
||||
//屏蔽promise wait的错误,因为错误信息已经在promise then的onRejected回调函数中返回
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
56
src/Handles/Apollo/Signature.php
Normal file
56
src/Handles/Apollo/Signature.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace StarPoolCloud\Handles\Apollo;
|
||||
|
||||
class Signature
|
||||
{
|
||||
/**
|
||||
* Authorization=Apollo {appId}:{sign}
|
||||
*/
|
||||
|
||||
const AUTHORIZATION_FORMAT = "Apollo %s:%s";
|
||||
const DELIMITER = "\n";
|
||||
const HTTP_HEADER_AUTHORIZATION = "Authorization";
|
||||
const HTTP_HEADER_TIMESTAMP = "Timestamp";
|
||||
|
||||
/**
|
||||
* 生成签名字符串
|
||||
* @param int $timestamp 13位时间戳
|
||||
* @param string $pathWithQuery 请求uri
|
||||
* @param string $secret 密钥
|
||||
* @return string
|
||||
*/
|
||||
public static function generateSignature(int $timestamp, string $pathWithQuery, string $secret): string
|
||||
{
|
||||
if (
|
||||
empty($timestamp) ||
|
||||
empty($pathWithQuery) ||
|
||||
empty($secret)
|
||||
) {
|
||||
return '';
|
||||
}
|
||||
return base64_encode(
|
||||
hash_hmac(
|
||||
'sha1',
|
||||
mb_convert_encoding($timestamp . self::DELIMITER . $pathWithQuery, "UTF-8"),
|
||||
$secret,
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成用于http请求的headers的认证字符串
|
||||
* @param string $appId 应用id
|
||||
* @param int $timestamp 13位时间戳
|
||||
* @param string $pathWithQuery 请求uri
|
||||
* @param string $secret 密钥
|
||||
* @return string
|
||||
*/
|
||||
public static function getAuthorizationString(string $appId, int $timestamp, string $pathWithQuery, string $secret): string
|
||||
{
|
||||
$sign = self::generateSignature($timestamp, $pathWithQuery, $secret);
|
||||
return sprintf(self::AUTHORIZATION_FORMAT, $appId, $sign);
|
||||
}
|
||||
}
|
262
src/Handles/File/code.php
Normal file
262
src/Handles/File/code.php
Normal file
@ -0,0 +1,262 @@
|
||||
<?php
|
||||
|
||||
const RESPONSE_CODE = [
|
||||
//服务器级别错误 10500 10999
|
||||
10501 => [
|
||||
'cn' => '写入错误',
|
||||
'en' => 'Write error',
|
||||
'hk' => '寫入錯誤',
|
||||
'ja' => '書き込みエラー',
|
||||
],
|
||||
10502 => [
|
||||
'cn' => '用户令牌无效',
|
||||
'en' => 'Invalid user token',
|
||||
'hk' => '用戶令牌無效',
|
||||
'ja' => '無効なユーザートークン',
|
||||
],
|
||||
10503 => [
|
||||
'cn' => '存储服务错误',
|
||||
'en' => 'Storage service error',
|
||||
'hk' => '存儲服務錯誤',
|
||||
'ja' => 'ストレージサービスエラー',
|
||||
],
|
||||
10504 => [
|
||||
'cn' => '数据库写入错误',
|
||||
'en' => 'Database write error',
|
||||
'hk' => '數據庫寫入錯誤',
|
||||
'ja' => 'データベース書き込みエラー',
|
||||
],
|
||||
10505 => [
|
||||
'cn' => '订单匹配错误',
|
||||
'en' => 'Order matching error',
|
||||
'hk' => '訂單匹配錯誤',
|
||||
'ja' => '注文照合エラー',
|
||||
],
|
||||
10506 => [
|
||||
'cn' => '口令请求错误',
|
||||
'en' => 'Token request error',
|
||||
'hk' => '口令請求錯誤',
|
||||
'ja' => 'パスワードリクエストエラー',
|
||||
],
|
||||
|
||||
|
||||
//业务级别 20500 - 20699
|
||||
|
||||
20501 => [
|
||||
'cn' => '参数缺失',
|
||||
'en' => 'Missing parameters',
|
||||
'hk' => '參數缺失',
|
||||
'ja' => 'パラメータがありません',
|
||||
],
|
||||
20502 => [
|
||||
'cn' => '暂无数据',
|
||||
'en' => 'No data',
|
||||
'hk' => '暫無數據',
|
||||
'ja' => 'データなし',
|
||||
],
|
||||
20503 => [
|
||||
'cn' => '商品/规格信息不匹配',
|
||||
'en' => 'Product/specification information does not match',
|
||||
'hk' => '商品/規格信息不匹配',
|
||||
'ja' => '製品/仕様情報が一致しません',
|
||||
],
|
||||
20504 => [
|
||||
'cn' => '订单信息错误',
|
||||
'en' => 'Incorrect order information',
|
||||
'hk' => '訂單信息錯誤',
|
||||
'ja' => '注文情報が正しくありません',
|
||||
],
|
||||
20505 => [
|
||||
'cn' => '没有文件需要上传',
|
||||
'en' => 'No files to upload',
|
||||
'hk' => '沒有文件需要上傳',
|
||||
'ja' => 'アップロードするファイルがありません',
|
||||
],
|
||||
20506 => [
|
||||
'cn' => '文件上传失败',
|
||||
'en' => 'File upload failed',
|
||||
'hk' => '文件上傳失敗',
|
||||
'ja' => 'ファイルのアップロードに失敗しました',
|
||||
],
|
||||
20507 => [
|
||||
'cn' => '参数格式错误',
|
||||
'en' => 'Parameter format error',
|
||||
'hk' => '參數格式錯誤',
|
||||
'ja' => 'パラメータフォーマットエラー',
|
||||
],
|
||||
20508 => [
|
||||
'cn' => '请注明审核失败原因',
|
||||
'en' => 'Please indicate the reason for the audit failure',
|
||||
'hk' => '請註明審核失敗原因',
|
||||
'ja' => '監査失敗の理由を示してください',
|
||||
],
|
||||
20509 => [
|
||||
'cn' => '未找到提现申请记录',
|
||||
'en' => 'No withdrawal application record found',
|
||||
'hk' => '未找到提現申請記錄',
|
||||
'ja' => '引き出し申請記録が見つかりません',
|
||||
],
|
||||
20510 => [
|
||||
'cn' => '请添加节点地址',
|
||||
'en' => 'Please add node address',
|
||||
'hk' => '請添加節點地址',
|
||||
'ja' => 'ノードアドレスを追加してください',
|
||||
],
|
||||
20511 => [
|
||||
'cn' => '商品信息错误',
|
||||
'en' => 'Product information error',
|
||||
'hk' => '商品信息錯誤',
|
||||
'ja' => '製品情報エラー',
|
||||
],
|
||||
20512 => [
|
||||
'cn' => '商品库存不足',
|
||||
'en' => 'Insufficient product inventory',
|
||||
'hk' => '商品庫存不足',
|
||||
'ja' => '不十分な製品在庫',
|
||||
],
|
||||
20513 => [
|
||||
'cn' => '图片资源不存在',
|
||||
'en' => 'Image resource does not exist',
|
||||
'hk' => '圖片資源不存在',
|
||||
'ja' => '画像リソースが存在しません',
|
||||
],
|
||||
20514 => [
|
||||
'cn' => '限时抢购商品活动资格不足',
|
||||
'en' => 'Insufficient qualifications for limited-time rush-buying activities',
|
||||
'hk' => '限時搶購商品活動資格不足',
|
||||
'ja' => '期間限定のラッシュバイ活動の資格が不十分',
|
||||
],
|
||||
20515 => [
|
||||
'cn' => '商品已下架,无法购买',
|
||||
'en' => 'The product has been removed and cannot be purchased',
|
||||
'hk' => '商品已下架,無法購買',
|
||||
'ja' => '製品は削除されており、購入できません',
|
||||
],
|
||||
20516 => [
|
||||
'cn' => '请先创建订单',
|
||||
'en' => 'Please create an order first',
|
||||
'hk' => '請先創建訂單',
|
||||
'ja' => '最初に注文を作成してください',
|
||||
],
|
||||
|
||||
|
||||
20601 => [
|
||||
'cn' => '用户未实名认证',
|
||||
'en' => 'User is not authenticated by real name',
|
||||
'hk' => '用戶未實名認證',
|
||||
'ja' => 'ユーザーは本名で認証されていません',
|
||||
],
|
||||
20602 => [
|
||||
'cn' => '登录密码错误',
|
||||
'en' => 'Incorrect login password',
|
||||
'hk' => '登錄密碼錯誤',
|
||||
'ja' => 'ログインパスワードが正しくありません',
|
||||
],
|
||||
20603 => [
|
||||
'cn' => '验证码不正确',
|
||||
'en' => 'Incorrect verification code',
|
||||
'hk' => '驗證碼不正確',
|
||||
'ja' => '不正な確認コード',
|
||||
],
|
||||
20604 => [
|
||||
'cn' => '邮箱校验失败',
|
||||
'en' => 'Email verification failed',
|
||||
'hk' => '郵箱校驗失敗',
|
||||
'ja' => 'メールの確認に失敗しました',
|
||||
],
|
||||
20605 => [
|
||||
'cn' => '发送邮箱失败',
|
||||
'en' => 'Failed to send mailbox',
|
||||
'hk' => '發送郵箱失敗',
|
||||
'ja' => 'メール送信に失敗しました',
|
||||
],
|
||||
20606 => [
|
||||
'cn' => '请勿重复申请',
|
||||
'en' => 'Please do not apply twice',
|
||||
'hk' => '請勿重複申請',
|
||||
'ja' => '二度と応募しないでください',
|
||||
],
|
||||
20607 => [
|
||||
'cn' => '余额不足',
|
||||
'en' => "Not sufficient funds",
|
||||
'hk' => '餘額不足',
|
||||
'ja' => '残高不足です',
|
||||
],
|
||||
|
||||
|
||||
//后台管理业务级 20700-20899
|
||||
20700 => [
|
||||
'cn' => '密码错误',
|
||||
'en' => 'wrong password',
|
||||
'hk' => '密碼錯誤',
|
||||
'ja' => '間違ったパスワード',
|
||||
],
|
||||
20701 => [
|
||||
'cn' => '重复发放',
|
||||
'en' => 'Reissue',
|
||||
'hk' => '重複發放',
|
||||
'ja' => '再発行',
|
||||
],
|
||||
20702 => [
|
||||
'cn' => '认证信息不存在',
|
||||
'en' => 'Authentication information does not exist',
|
||||
'hk' => '認證信息不存在',
|
||||
'ja' => '認証情報が存在しません',
|
||||
],
|
||||
20703 => [
|
||||
'cn' => '未配置佣金比例,请联系管理员',
|
||||
'en' => 'Commission rate is not configured, please contact the administrator',
|
||||
'hk' => '未配置佣金比例,請聯繫管理員',
|
||||
'ja' => '手数料率が設定されていません。管理者に連絡してください',
|
||||
],
|
||||
20704 => [
|
||||
'cn' => '该系列类型未按照日期顺序发放,请检查后重试',
|
||||
'en' => 'This series type is not issued in the order of date, please check and try again',
|
||||
'hk' => '該系列類型未按照日期順序發放,請檢查後重試',
|
||||
'ja' => 'このシリーズタイプは日付順に発行されていませんので、ご確認の上、お試しください。',
|
||||
],
|
||||
20705 => [
|
||||
'cn' => '该系列类型已经存在相同合约周期',
|
||||
'en' => 'The same contract period already exists for this series type',
|
||||
'hk' => '該系列類型已經存在相同合約週期',
|
||||
'ja' => 'このシリーズタイプには、同じ契約期間がすでに存在します',
|
||||
],
|
||||
20706 => [
|
||||
|
||||
'cn' => '无操作权限',
|
||||
'en' => 'No operation authority',
|
||||
'hk' => '無操作許可權',
|
||||
'ja' => '操作権限がありません',
|
||||
],
|
||||
|
||||
20707 => [
|
||||
'cn' => '用户的质押币余额不足',
|
||||
'en' => "The user's staking currency balance is insufficient",
|
||||
'hk' => '用戶的質押幣餘額不足',
|
||||
'ja' => 'ユーザーの賭け通貨残高が不十分です',
|
||||
],
|
||||
|
||||
20708 => [
|
||||
'cn' => '在线获取质押比例失败,请检查服务网络连接',
|
||||
'en' => "Failed to obtain the pledge ratio online, please check the service network connection",
|
||||
'hk' => '在線獲取質押比例失敗,請檢查服務網絡連接',
|
||||
'ja' => 'オンラインで質権比率を取得できませんでした。サービスネットワーク接続を確認してください。',
|
||||
],
|
||||
|
||||
50000 => [
|
||||
'cn' => '处理失败',
|
||||
'en' => 'Processing failed',
|
||||
'hk' => '處理失敗',
|
||||
'ja' => '処理に失敗しました',
|
||||
],
|
||||
];
|
||||
|
||||
|
||||
function GetErrorMessage(int $code, string $lang = 'cn'): string
|
||||
{
|
||||
$lang = strtolower($lang);
|
||||
if (!in_array($lang, ['en', 'hk', 'ja', 'cn'])) {
|
||||
$lang = 'cn';
|
||||
}
|
||||
return RESPONSE_CODE[$code][$lang] ?? '未定义的错误';
|
||||
}
|
131
src/Handles/File/helper.php
Normal file
131
src/Handles/File/helper.php
Normal file
@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
function getLanguage(): string
|
||||
{
|
||||
$lang = request()->header('Accept-Language');
|
||||
if (!in_array($lang, ['ja', 'en', 'hk'])) {
|
||||
$lang = 'cn';
|
||||
}
|
||||
return $lang;
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败响应
|
||||
* @param int $code
|
||||
* @return JsonResponse
|
||||
*/
|
||||
function fail(int $code): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'code' => $code,
|
||||
'message' => GetErrorMessage($code, getLanguage())
|
||||
], 417);
|
||||
}
|
||||
|
||||
/**
|
||||
* 正常响应
|
||||
* @param Collection $data
|
||||
* @return JsonResponse
|
||||
*/
|
||||
function success(Collection $data): JsonResponse
|
||||
{
|
||||
return response()->json($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数响应
|
||||
* @param array $errors
|
||||
* @return JsonResponse
|
||||
*/
|
||||
function parameter(array $errors): JsonResponse
|
||||
{
|
||||
return response()->json(['errors' => $errors], 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误响应
|
||||
* @param int $status
|
||||
* @param string $error
|
||||
* @return JsonResponse
|
||||
*/
|
||||
function error(int $status, string $error): JsonResponse
|
||||
{
|
||||
return response()->json(['error' => $error], $status);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取订单号
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
function getOrderNumber(): string
|
||||
{
|
||||
$carbon = new Carbon();
|
||||
$parse = $carbon::parse('2021-06-01 00:00:00');
|
||||
$day = $parse->diffInDays($carbon::now());
|
||||
$prefix = str_pad($day, 5, '0', STR_PAD_LEFT);
|
||||
$pow = pow(10, 10);
|
||||
$random_int = random_int(0, $pow - 1);
|
||||
return $prefix . str_pad($random_int, 10, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取毫秒时间戳
|
||||
* @return string
|
||||
*/
|
||||
function getMicroTime(): string
|
||||
{
|
||||
return bcmul(microtime(true), 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $ip
|
||||
* @param string $type
|
||||
* * @return string|bool
|
||||
* @note: 获取ip地址信息
|
||||
* @throws Exception
|
||||
* @author: fanete
|
||||
* @time: 2021/7/28 11:24 下午
|
||||
*/
|
||||
function getIpInfo(string $ip, string $type = 'all'): string|bool
|
||||
{
|
||||
|
||||
$ip2region = new Ip2Region();
|
||||
try {
|
||||
$info = $ip2region->btreeSearch($ip);
|
||||
if ($info) {
|
||||
$region = explode('|', $info['region']);
|
||||
return match ($type) {
|
||||
'country' => $region[0],
|
||||
'province' => $region[2],
|
||||
'city' => $region[3],
|
||||
'detail' => $region[4],
|
||||
default => json_encode($region),
|
||||
};
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} catch (Exception) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function printSQL()
|
||||
{
|
||||
DB::listen(function ($query) {
|
||||
$bindings = $query->bindings;
|
||||
$sql = $query->sql;
|
||||
foreach ($bindings as $replace) {
|
||||
$value = is_numeric($replace) ? $replace : "'" . $replace . "'";
|
||||
$sql = preg_replace('/\?/', $value, $sql, 1);
|
||||
}
|
||||
dump($sql);
|
||||
});
|
||||
}
|
||||
|
||||
|
227
src/Handles/StarPoolCloudHandle.php
Normal file
227
src/Handles/StarPoolCloudHandle.php
Normal file
@ -0,0 +1,227 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace StarPoolCloud\Handles;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\RequestOptions;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Ramsey\Uuid\Rfc4122\UuidV4;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
|
||||
class StarPoolCloudHandle
|
||||
{
|
||||
const DATE_FORMAT = 'Y-m-d H:i:s';
|
||||
|
||||
private static Client $client;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
self::$client = new Client();
|
||||
}
|
||||
|
||||
/**
|
||||
* Aes加密
|
||||
* @param string $data
|
||||
* @param string $key
|
||||
* @param string $vi
|
||||
* @return string|false
|
||||
*/
|
||||
public function AesEncrypt(string $data, string $key, string $vi): string|false
|
||||
{
|
||||
return $this->Base64UrlEncode(openssl_encrypt($data, 'AES-128-CBC', $key, OPENSSL_CIPHER_AES_128_CBC, $vi));
|
||||
}
|
||||
|
||||
/**
|
||||
* Aes解密
|
||||
* @param string $data
|
||||
* @param string $key
|
||||
* @param string $vi
|
||||
* @return string|false
|
||||
*/
|
||||
public function AesDecrypt(string $data, string $key, string $vi): string|false
|
||||
{
|
||||
return openssl_decrypt($this->Base64UrlDecode($data), 'AES-128-CBC', $key, OPENSSL_CIPHER_AES_128_CBC, $vi);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
* @param bool $strict
|
||||
* @return string
|
||||
*/
|
||||
public function Base64UrlEncode(string $data, bool $strict = false): string
|
||||
{
|
||||
if ($strict) {
|
||||
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
|
||||
}
|
||||
return str_replace(['+', '/'], ['-', '_'], base64_encode($data));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
* @param bool $strict
|
||||
* @return string
|
||||
*/
|
||||
public function Base64UrlDecode(string $data, bool $strict = false): string
|
||||
{
|
||||
if ($strict) {
|
||||
return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT));
|
||||
}
|
||||
return base64_decode(str_replace(['-', '_'], ['+', '/'], $data));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @param array $params
|
||||
* @param array $header
|
||||
* @return string
|
||||
*/
|
||||
public function PostJsonRequest(string $url, array $params, array $header = []): string
|
||||
{
|
||||
try {
|
||||
$response = self::$client->post($url, [
|
||||
RequestOptions::JSON => $params,
|
||||
RequestOptions::HEADERS => $header
|
||||
]);
|
||||
return $response->getBody()->getContents();
|
||||
} catch (GuzzleException $exception) {
|
||||
Log::error("SignatureRequestHttpPostByAes Error", [
|
||||
'msg' => $exception->getMessage()
|
||||
]);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 签名调用请求
|
||||
*
|
||||
* @param string $url
|
||||
* @param $productID
|
||||
* @param string $key
|
||||
* @param string $vi
|
||||
* @param array $params
|
||||
* @return string|false
|
||||
*/
|
||||
public function SignatureRequestHttpPostByAes(string $url, $productID, string $key, string $vi, array $params): string|false
|
||||
{
|
||||
$timestamp = time();
|
||||
$form = array_merge($params, [
|
||||
't' => $timestamp
|
||||
]);
|
||||
ksort($form);
|
||||
$http_build_query = http_build_query($form);
|
||||
try {
|
||||
$signature = $this->AesEncrypt($http_build_query, $key, $vi);
|
||||
if ($signature === false) {
|
||||
return false;
|
||||
}
|
||||
$response = self::$client->post($url, [
|
||||
RequestOptions::FORM_PARAMS => $form,
|
||||
RequestOptions::HEADERS => [
|
||||
'Signature' => $signature,
|
||||
'Signature-Date' => date($this::DATE_FORMAT, $timestamp),
|
||||
'Product-ID' => $productID
|
||||
]
|
||||
]);
|
||||
} catch (GuzzleException $exception) {
|
||||
Log::error("SignatureRequestHttpPostByAes Error", [
|
||||
'msg' => $exception->getMessage()
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
return $response->getBody()->getContents();
|
||||
}
|
||||
|
||||
/**
|
||||
* 钉钉报警
|
||||
*
|
||||
* @param string $accessToken
|
||||
* @param string $secret
|
||||
* @param array $params
|
||||
* @return bool
|
||||
*/
|
||||
public function DingTalkAlert(string $accessToken, string $secret, array $params): bool
|
||||
{
|
||||
try {
|
||||
$timestamp = getMicroTime();
|
||||
$signData = $timestamp . "\n" . $secret;
|
||||
$hash_hmac = hash_hmac('sha256', $signData, $secret, true);
|
||||
$sign = urlencode(base64_encode($hash_hmac));
|
||||
$httpQuery = http_build_query([
|
||||
'access_token' => $accessToken,
|
||||
'sign' => $sign,
|
||||
'timestamp' => $timestamp,
|
||||
]);
|
||||
$response = self::$client->post('https://oapi.dingtalk.com/robot/send?' . $httpQuery, [
|
||||
RequestOptions::JSON => $params,
|
||||
RequestOptions::HEADERS => [
|
||||
'Content-Type' => 'application/json;charset=utf-8'
|
||||
]
|
||||
]);
|
||||
$body = json_decode($response->getBody()->getContents(), true);
|
||||
return ($body['errcode'] ?? false) === 0;
|
||||
} catch (GuzzleException $exception) {
|
||||
Log::error("DingTalkAlert Error", [
|
||||
'msg' => $exception->getMessage()
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 有道翻译
|
||||
*
|
||||
* @param string $appID
|
||||
* @param string $secret
|
||||
* @param string $text
|
||||
* @param string $lang
|
||||
* @return string
|
||||
*/
|
||||
function YouDaoTextTranslator(string $appID, string $secret, string $text, string $lang): string
|
||||
{
|
||||
if ($lang === 'hk') {
|
||||
$lang = 'zh-CHT';
|
||||
}
|
||||
$uuid4 = Uuid::uuid4();
|
||||
$uuid = $uuid4->toString();
|
||||
$timestamp = (string)now()->utc()->timestamp;
|
||||
$len = mb_strlen($text);
|
||||
if ($len > 20) {
|
||||
$input = mb_substr($text, 0, 10) . $len . mb_substr($text, $len - 10, $len);
|
||||
$signStr = $appID . $input . $uuid . $timestamp . $secret;
|
||||
} else {
|
||||
$signStr = $appID . $text . $uuid . $timestamp . $secret;
|
||||
}
|
||||
$sign = hash('sha256', $signStr);
|
||||
|
||||
try {
|
||||
$response = self::$client->post("https://openapi.youdao.com/api", [
|
||||
RequestOptions::FORM_PARAMS => [
|
||||
'q' => $text,
|
||||
'from' => 'zh-CHS',
|
||||
'to' => $lang,
|
||||
'appKey' => $appID,
|
||||
'salt' => $uuid,
|
||||
'sign' => $sign,
|
||||
'signType' => "v3",
|
||||
'curtime' => $timestamp,
|
||||
'strict' => "true",
|
||||
],
|
||||
RequestOptions::HEADERS => [
|
||||
'Content-Type' => 'application/x-www-form-urlencoded;charset=utf-8'
|
||||
]
|
||||
]);
|
||||
$body = json_decode($response->getBody()->getContents(), true);
|
||||
if (($body['errorCode'] ?? false) === '0') {
|
||||
return $body['translation'][0];
|
||||
}
|
||||
return "";
|
||||
} catch (GuzzleException $e) {
|
||||
Log::error("YouDaoTextTranslator Error", [
|
||||
'msg' => $e->getMessage()
|
||||
]);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
124
src/Handles/Trait/ThrottlesLogins.php
Normal file
124
src/Handles/Trait/ThrottlesLogins.php
Normal file
@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace StarPoolCloud\Handles\Trait;
|
||||
|
||||
use Illuminate\Auth\Events\Lockout;
|
||||
use Illuminate\Cache\RateLimiter;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
trait ThrottlesLogins
|
||||
{
|
||||
/**
|
||||
* Determine if the user has too many failed login attempts.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasTooManyLoginAttempts(Request $request)
|
||||
{
|
||||
return $this->limiter()->tooManyAttempts(
|
||||
$this->throttleKey($request), $this->maxAttempts()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the login attempts for the user.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return void
|
||||
*/
|
||||
protected function incrementLoginAttempts(Request $request)
|
||||
{
|
||||
$this->limiter()->hit(
|
||||
$this->throttleKey($request), $this->decayMinutes() * 60
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect the user after determining they are locked out.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return void
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
protected function sendLockoutResponse(Request $request)
|
||||
{
|
||||
$seconds = $this->limiter()->availableIn(
|
||||
$this->throttleKey($request)
|
||||
);
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
$this->username() => [trans('auth.throttle', [
|
||||
'seconds' => $seconds,
|
||||
'minutes' => ceil($seconds / 60),
|
||||
])],
|
||||
])->status(Response::HTTP_TOO_MANY_REQUESTS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the login locks for the given user credentials.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return void
|
||||
*/
|
||||
protected function clearLoginAttempts(Request $request)
|
||||
{
|
||||
$this->limiter()->clear($this->throttleKey($request));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire an event when a lockout occurs.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return void
|
||||
*/
|
||||
protected function fireLockoutEvent(Request $request)
|
||||
{
|
||||
event(new Lockout($request));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the throttle key for the given request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return string
|
||||
*/
|
||||
protected function throttleKey(Request $request)
|
||||
{
|
||||
return Str::lower($request->input($this->username())) . '|' . $request->ip();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the rate limiter instance.
|
||||
*
|
||||
* @return \Illuminate\Cache\RateLimiter
|
||||
*/
|
||||
protected function limiter()
|
||||
{
|
||||
return app(RateLimiter::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum number of attempts to allow.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function maxAttempts()
|
||||
{
|
||||
return property_exists($this, 'maxAttempts') ? $this->maxAttempts : 5;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of minutes to throttle for.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function decayMinutes()
|
||||
{
|
||||
return property_exists($this, 'decayMinutes') ? $this->decayMinutes : 1;
|
||||
}
|
||||
}
|
45
src/Providers/StarPoolCloudServiceProvider.php
Normal file
45
src/Providers/StarPoolCloudServiceProvider.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace StarPoolCloud\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use StarPoolCloud\Commands\ApolloConfigSync;
|
||||
use StarPoolCloud\Handles\Apollo\Client;
|
||||
use StarPoolCloud\Handles\StarPoolCloudHandle;
|
||||
|
||||
|
||||
class StarPoolCloudServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Bootstrap the application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
$this->publishes([
|
||||
realpath(__DIR__ . '/../../config/apollo.php') => config_path('apollo.php')
|
||||
]);
|
||||
if ($this->app->runningInConsole()) {
|
||||
$this->commands([
|
||||
ApolloConfigSync::class
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the service provider.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$this->app->singleton('StarPoolCloud', function () {
|
||||
return new StarPoolCloudHandle();
|
||||
});
|
||||
$this->app->singleton('StarPoolCloudApollo', function () {
|
||||
return new Client(config('apollo'));
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user