first commit

This commit is contained in:
BvBeJ 2021-11-30 21:24:49 +08:00
commit 08da50a0b7
13 changed files with 1433 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
/.idea/
/vendor/
/test/
composer.lock

3
README.md Normal file
View File

@ -0,0 +1,3 @@
```shell
php artisan vendor:publish --provider=StarPoolCloud\Providers\StarPoolCloudServiceProvider
```

39
composer.json Normal file
View 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
View 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', '')
];

View 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()}");
}
}
}

View 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';
}
}

View 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&notifications=$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回调函数中返回
}
}
}
}

View 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
View 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
View 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);
});
}

View 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 "";
}
}
}

View 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;
}
}

View 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'));
});
}
}