wuearl
2 years ago
commit
74656dba66
71 changed files with 6100 additions and 0 deletions
@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
|
||||
root = true |
||||
|
||||
[*] |
||||
indent_style = space |
||||
indent_size = 4 |
||||
end_of_line = lf |
||||
charset = utf-8 |
||||
trim_trailing_whitespace = true |
||||
insert_final_newline = false |
||||
|
||||
[*.{vue,js,scss}] |
||||
charset = utf-8 |
||||
indent_style = space |
||||
indent_size = 2 |
||||
end_of_line = lf |
||||
insert_final_newline = true |
||||
trim_trailing_whitespace = true |
||||
|
||||
[*.md] |
||||
trim_trailing_whitespace = false |
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
/.idea |
||||
/.git |
||||
/.vscode |
||||
/vendor |
||||
*.log |
||||
.env |
||||
index.php |
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
path: ./ |
||||
jobs: 10 |
||||
exclude: |
||||
- .github |
||||
- build |
||||
- vendor |
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
preset: symfony |
||||
|
||||
enabled: |
||||
- strict |
||||
- strict_param |
||||
- ordered_use |
@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
|
||||
{ |
||||
"name": "wuearl\/bytedance", |
||||
"description": "sdk for bytedance (douyin, toutiao, huoshan, xigua)", |
||||
"license": "MIT", |
||||
"authors": [ |
||||
{ |
||||
"name": "wuearl", |
||||
"email": "wuearl@qq.com" |
||||
} |
||||
], |
||||
"require": { |
||||
"php": ">=7.1", |
||||
"ext-openssl": "*", |
||||
"ext-json": "*", |
||||
"ext-fileinfo": "*", |
||||
"psr/simple-cache": "^1.0", |
||||
"guzzlehttp/guzzle": "^6.2", |
||||
"monolog/monolog": "^1.22 || ^2.0", |
||||
"symfony/cache": "^3.3 || ^4.3", |
||||
"pimple/pimple": "^3.0", |
||||
"symfony/psr-http-message-bridge": "^0.3 || ^1.0", |
||||
"symfony/http-foundation": "^2.7 || ^3.0 || ^4.0" |
||||
}, |
||||
"autoload": { |
||||
"psr-4": { |
||||
"ByteDance\\": "src" |
||||
}, |
||||
"files": [ |
||||
"src/Kernel/Support/Helpers.php", |
||||
"src/Kernel/Helpers.php" |
||||
] |
||||
}, |
||||
"require-dev": { |
||||
"phpunit/phpunit": "^7.5", |
||||
"overtrue/phplint": "^1.2" |
||||
} |
||||
} |
@ -0,0 +1,47 @@
@@ -0,0 +1,47 @@
|
||||
<?php |
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance; |
||||
|
||||
/** |
||||
* Class Factory. |
||||
* |
||||
* @method static \ByteDance\MiniProgram\Application miniProgram(array $config) |
||||
* @method static \ByteDance\Payment\Application payment(array $config) |
||||
*/ |
||||
class Factory |
||||
{ |
||||
/** |
||||
* @param $name |
||||
* @param array $config |
||||
* |
||||
* @return \ByteDance\Kernel\ServiceContainer |
||||
*/ |
||||
public static function make($name, array $config) |
||||
{ |
||||
$namespace = Kernel\Support\Str::studly($name); |
||||
$application = "\\ByteDance\\{$namespace}\\Application"; |
||||
|
||||
return new $application($config); |
||||
} |
||||
|
||||
/** |
||||
* Dynamically pass methods to the application. |
||||
* |
||||
* @param string $name |
||||
* @param array $arguments |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public static function __callStatic($name, $arguments) |
||||
{ |
||||
return self::make($name, ...$arguments); |
||||
} |
||||
} |
@ -0,0 +1,259 @@
@@ -0,0 +1,259 @@
|
||||
<?php |
||||
/* |
||||
* This file is part of the OtkurBiz/ByteDance. |
||||
* |
||||
* (c) |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel; |
||||
|
||||
use ByteDance\Kernel\Contracts\AccessTokenInterface; |
||||
use ByteDance\Kernel\Exceptions\HttpException; |
||||
use ByteDance\Kernel\Exceptions\InvalidArgumentException; |
||||
use ByteDance\Kernel\Exceptions\RuntimeException; |
||||
use ByteDance\Kernel\Traits\HasHttpRequests; |
||||
use ByteDance\Kernel\Traits\InteractsWithCache; |
||||
use Pimple\Container; |
||||
use Psr\Http\Message\RequestInterface; |
||||
use Psr\Http\Message\ResponseInterface; |
||||
|
||||
/** |
||||
* Class AccessToken. |
||||
* |
||||
* @author |
||||
*/ |
||||
abstract class AccessToken implements AccessTokenInterface |
||||
{ |
||||
use HasHttpRequests; |
||||
use InteractsWithCache; |
||||
/** |
||||
* @var \Pimple\Container |
||||
*/ |
||||
protected $app; |
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $requestMethod = 'GET'; |
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $endpointToGetToken; |
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $queryName; |
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $token; |
||||
|
||||
/** |
||||
* @var int |
||||
*/ |
||||
protected $safeSeconds = 500; |
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $tokenKey = 'access_token'; |
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $cachePrefix = 'otkurbiz.bytedance.kernel.access_token.'; |
||||
|
||||
/** |
||||
* AccessToken constructor. |
||||
* |
||||
* @param \Pimple\Container $app |
||||
*/ |
||||
public function __construct(Container $app) |
||||
{ |
||||
$this->app = $app; |
||||
} |
||||
|
||||
/** |
||||
* @param bool $refresh |
||||
* @return array |
||||
* @throws HttpException |
||||
* @throws InvalidArgumentException |
||||
* @throws RuntimeException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
* @throws \Psr\SimpleCache\InvalidArgumentException |
||||
*/ |
||||
public function getToken(bool $refresh = false): array |
||||
{ |
||||
$cacheKey = $this->getCacheKey(); |
||||
$cache = $this->getCache(); |
||||
if (!$refresh && $cache->has($cacheKey)) { |
||||
return $cache->get($cacheKey); |
||||
} |
||||
$token = $this->requestToken($this->getCredentials(), true); |
||||
$this->setToken($token, $token['expires_in'] ?? 7200); |
||||
|
||||
return $token; |
||||
} |
||||
|
||||
/** |
||||
* @param string $token |
||||
* @param int $lifetime |
||||
* |
||||
* @throws \Psr\SimpleCache\InvalidArgumentException |
||||
* @throws \ByteDance\Kernel\Exceptions\RuntimeException |
||||
* |
||||
* @return \ByteDance\Kernel\Contracts\AccessTokenInterface |
||||
*/ |
||||
public function setToken(array $token, $lifetime = 7200): AccessTokenInterface |
||||
{ |
||||
$this->getCache()->set($this->getCacheKey(), [ |
||||
$this->tokenKey => $token[$this->tokenKey], |
||||
'expires_in' => $lifetime, |
||||
], $lifetime - $this->safeSeconds); |
||||
|
||||
if (!$this->getCache()->has($this->getCacheKey())) { |
||||
throw new RuntimeException('Failed to cache access token.'); |
||||
} |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* @return AccessTokenInterface |
||||
* @throws HttpException |
||||
* @throws InvalidArgumentException |
||||
* @throws RuntimeException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
* @throws \Psr\SimpleCache\InvalidArgumentException |
||||
*/ |
||||
public function refresh(): AccessTokenInterface |
||||
{ |
||||
$this->getToken(true); |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* @param array $credentials |
||||
* @param bool $toArray |
||||
* @return array|Support\Collection|mixed|object|ResponseInterface|string |
||||
* @throws HttpException |
||||
* @throws InvalidArgumentException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function requestToken(array $credentials, $toArray = false) |
||||
{ |
||||
$response = $this->sendRequest($credentials); |
||||
$result = json_decode($response->getBody()->getContents(), true); |
||||
$formatted = $this->castResponseToType($response, $this->app['config']->get('response_type')); |
||||
if (empty($result[$this->tokenKey])) { |
||||
throw new HttpException('Request access_token fail: '.json_encode($result, JSON_UNESCAPED_UNICODE), $response, $formatted); |
||||
} |
||||
|
||||
return $toArray ? $result : $formatted; |
||||
} |
||||
|
||||
/** |
||||
* @param RequestInterface $request |
||||
* @param array $requestOptions |
||||
* @return RequestInterface |
||||
* @throws HttpException |
||||
* @throws InvalidArgumentException |
||||
* @throws RuntimeException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
* @throws \Psr\SimpleCache\InvalidArgumentException |
||||
*/ |
||||
public function applyToRequest(RequestInterface $request, array $requestOptions = []): RequestInterface |
||||
{ |
||||
parse_str($request->getUri()->getQuery(), $query); |
||||
$query = http_build_query(array_merge($this->getQuery(), $query)); |
||||
|
||||
return $request->withUri($request->getUri()->withQuery($query)); |
||||
} |
||||
|
||||
/** |
||||
* @param array $requestOptions |
||||
* @return array |
||||
* @throws HttpException |
||||
* @throws InvalidArgumentException |
||||
* @throws RuntimeException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
* @throws \Psr\SimpleCache\InvalidArgumentException |
||||
*/ |
||||
public function applyToOptions(array $requestOptions = []): array |
||||
{ |
||||
if(isset($requestOptions['json'])){ |
||||
$requestOptions['json'] = array_merge($this->getQuery(), $requestOptions['json']); |
||||
} |
||||
return $requestOptions; |
||||
} |
||||
|
||||
/** |
||||
* Send http request. |
||||
* |
||||
* @param array $credentials |
||||
* |
||||
* @return ResponseInterface |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
* |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidArgumentException |
||||
*/ |
||||
protected function sendRequest(array $credentials): ResponseInterface |
||||
{ |
||||
$options = [ |
||||
('GET' === $this->requestMethod) ? 'query' : 'json' => $credentials, |
||||
]; |
||||
|
||||
return $this->setHttpClient($this->app['http_client'])->request($this->getEndpoint(), $this->requestMethod, $options); |
||||
} |
||||
|
||||
/** |
||||
* @return string |
||||
*/ |
||||
protected function getCacheKey() |
||||
{ |
||||
return $this->cachePrefix.md5(json_encode($this->getCredentials())); |
||||
} |
||||
|
||||
/** |
||||
* @return array |
||||
* @throws HttpException |
||||
* @throws InvalidArgumentException |
||||
* @throws RuntimeException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
* @throws \Psr\SimpleCache\InvalidArgumentException |
||||
*/ |
||||
protected function getQuery(): array |
||||
{ |
||||
return [$this->queryName ?? $this->tokenKey => $this->getToken()[$this->tokenKey]]; |
||||
} |
||||
|
||||
/** |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidArgumentException |
||||
* |
||||
* @return string |
||||
*/ |
||||
public function getEndpoint(): string |
||||
{ |
||||
if (empty($this->endpointToGetToken)) { |
||||
throw new InvalidArgumentException('No endpoint for access token request.'); |
||||
} |
||||
|
||||
return $this->endpointToGetToken; |
||||
} |
||||
|
||||
/** |
||||
* @return string |
||||
*/ |
||||
public function getTokenKey() |
||||
{ |
||||
return $this->tokenKey; |
||||
} |
||||
|
||||
/** |
||||
* Credential for get token. |
||||
* |
||||
* @return array |
||||
*/ |
||||
abstract protected function getCredentials(): array; |
||||
} |
@ -0,0 +1,254 @@
@@ -0,0 +1,254 @@
|
||||
<?php |
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel; |
||||
|
||||
use GuzzleHttp\MessageFormatter; |
||||
use GuzzleHttp\Middleware; |
||||
use ByteDance\Kernel\Contracts\AccessTokenInterface; |
||||
use ByteDance\Kernel\Http\Response; |
||||
use ByteDance\Kernel\Traits\HasHttpRequests; |
||||
use Psr\Http\Message\RequestInterface; |
||||
use Psr\Http\Message\ResponseInterface; |
||||
|
||||
/** |
||||
* Class BaseClient. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class BaseClient |
||||
{ |
||||
use HasHttpRequests { request as performRequest; } |
||||
/** |
||||
* @var \ByteDance\Kernel\ServiceContainer |
||||
*/ |
||||
protected $app; |
||||
/** |
||||
* @var \ByteDance\Kernel\Contracts\AccessTokenInterface |
||||
*/ |
||||
protected $accessToken; |
||||
/** |
||||
* @var |
||||
*/ |
||||
protected $baseUri; |
||||
|
||||
/** |
||||
* BaseClient constructor. |
||||
* |
||||
* @param \ByteDance\Kernel\ServiceContainer $app |
||||
* @param \ByteDance\Kernel\Contracts\AccessTokenInterface|null $accessToken |
||||
*/ |
||||
public function __construct(ServiceContainer $app, AccessTokenInterface $accessToken = null) |
||||
{ |
||||
$this->app = $app; |
||||
$this->accessToken = $accessToken ?? $this->app['access_token']; |
||||
} |
||||
|
||||
/** |
||||
* GET request. |
||||
* @param string $url |
||||
* @param array $query |
||||
* @return array|Support\Collection|object|ResponseInterface|string |
||||
* @throws Exceptions\InvalidArgumentException |
||||
* @throws Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function httpGet(string $url, array $query = []) |
||||
{ |
||||
return $this->request($url, 'GET', ['query' => $query]); |
||||
} |
||||
|
||||
/** |
||||
* POST request. |
||||
* @param string $url |
||||
* @param array $data |
||||
* @return array|Support\Collection|object|ResponseInterface|string |
||||
* @throws Exceptions\InvalidArgumentException |
||||
* @throws Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function httpPost(string $url, array $data = []) |
||||
{ |
||||
return $this->request($url, 'POST', ['form_params' => $data]); |
||||
} |
||||
|
||||
/** |
||||
* JSON request. |
||||
* @param string $url |
||||
* @param array $data |
||||
* @param array $query |
||||
* @return array|Support\Collection|object|ResponseInterface|string |
||||
* @throws Exceptions\InvalidArgumentException |
||||
* @throws Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function httpPostJson(string $url, array $data = [], array $query = []) |
||||
{ |
||||
return $this->request($url, 'POST', ['query' => $query, 'json' => $data],true); |
||||
} |
||||
|
||||
/** |
||||
* Upload file. |
||||
* @param string $url |
||||
* @param array $files |
||||
* @param array $form |
||||
* @param array $query |
||||
* @return array|Support\Collection|object|ResponseInterface|string |
||||
* @throws Exceptions\InvalidArgumentException |
||||
* @throws Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function httpUpload(string $url, array $files = [], array $form = [], array $query = []) |
||||
{ |
||||
$multipart = []; |
||||
foreach ($files as $name => $path) { |
||||
$multipart[] = [ |
||||
'name' => $name, |
||||
'contents' => fopen($path, 'r'), |
||||
]; |
||||
} |
||||
foreach ($form as $name => $contents) { |
||||
$multipart[] = compact('name', 'contents'); |
||||
} |
||||
|
||||
return $this->request($url, 'POST', ['query' => $query, 'multipart' => $multipart, 'connect_timeout' => 30, 'timeout' => 30, 'read_timeout' => 30]); |
||||
} |
||||
|
||||
/** |
||||
* @return AccessTokenInterface |
||||
*/ |
||||
public function getAccessToken(): AccessTokenInterface |
||||
{ |
||||
return $this->accessToken; |
||||
} |
||||
|
||||
/** |
||||
* @param \ByteDance\Kernel\Contracts\AccessTokenInterface $accessToken |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function setAccessToken(AccessTokenInterface $accessToken) |
||||
{ |
||||
$this->accessToken = $accessToken; |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* @param string $url |
||||
* @param string $method |
||||
* @param array $options |
||||
* @param bool $returnRaw |
||||
* |
||||
* @return \Psr\Http\Message\ResponseInterface|\ByteDance\Kernel\Support\Collection|array|object|string |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
* |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidConfigException |
||||
* @throws Exceptions\InvalidArgumentException |
||||
*/ |
||||
public function request(string $url, string $method = 'GET', array $options = [], $returnRaw = false) |
||||
{ |
||||
if (empty($this->middlewares)) { |
||||
$this->registerHttpMiddlewares(); |
||||
if ($this->accessToken) { |
||||
$options = $this->accessToken->applyToOptions($options); |
||||
} |
||||
} |
||||
$response = $this->performRequest($url, $method, $options); |
||||
|
||||
return $returnRaw ? $response : $this->castResponseToType($response, $this->app->config->get('response_type')); |
||||
} |
||||
|
||||
/** |
||||
* @param string $url |
||||
* @param string $method |
||||
* @param array $options |
||||
* @return Response |
||||
* @throws Exceptions\InvalidArgumentException |
||||
* @throws Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function requestRaw(string $url, string $method = 'GET', array $options = []) |
||||
{ |
||||
return Response::buildFromPsrResponse($this->request($url, $method, $options, true)); |
||||
} |
||||
|
||||
/** |
||||
* Register Guzzle middlewares. |
||||
*/ |
||||
protected function registerHttpMiddlewares() |
||||
{ |
||||
// retry |
||||
$this->pushMiddleware($this->retryMiddleware(), 'retry'); |
||||
// access token |
||||
$this->pushMiddleware($this->accessTokenMiddleware(), 'access_token'); |
||||
// log |
||||
$this->pushMiddleware($this->logMiddleware(), 'log'); |
||||
} |
||||
|
||||
/** |
||||
* Attache access token to request query. |
||||
* |
||||
* @return \Closure |
||||
*/ |
||||
protected function accessTokenMiddleware() |
||||
{ |
||||
return function (callable $handler) { |
||||
return function (RequestInterface $request, array $options) use ($handler) { |
||||
if ($this->accessToken) { |
||||
$request = $this->accessToken->applyToRequest($request, $options); |
||||
} |
||||
return $handler($request, $options); |
||||
}; |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* Log the request. |
||||
* |
||||
* @return \Closure |
||||
*/ |
||||
protected function logMiddleware() |
||||
{ |
||||
$formatter = new MessageFormatter($this->app['config']['http.log_template'] ?? MessageFormatter::DEBUG); |
||||
|
||||
return Middleware::log($this->app['logger'], $formatter); |
||||
} |
||||
|
||||
/** |
||||
* Return retry middleware. |
||||
* |
||||
* @return \Closure |
||||
*/ |
||||
protected function retryMiddleware() |
||||
{ |
||||
return Middleware::retry(function ( |
||||
$retries, |
||||
RequestInterface $request, |
||||
ResponseInterface $response = null |
||||
) { |
||||
// Limit the number of retries to 2 |
||||
if ($retries < $this->app->config->get('http.max_retries', 1) && $response && $body = $response->getBody()) { |
||||
// Retry on server errors |
||||
$response = json_decode($body, true); |
||||
if (!empty($response['errcode']) && in_array(abs($response['errcode']), [40001, 40002, 40014, 42001], true)) { |
||||
$this->accessToken->refresh(); |
||||
$this->app['logger']->debug('Retrying with refreshed access token.'); |
||||
|
||||
return true; |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
}, function () { |
||||
return abs($this->app->config->get('http.retry_delay', 500)); |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,63 @@
@@ -0,0 +1,63 @@
|
||||
<?php |
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel\Clauses; |
||||
|
||||
/** |
||||
* Class Clause. |
||||
* |
||||
* @author mingyoung <mingyoungcheung@gmail.com> |
||||
*/ |
||||
class Clause |
||||
{ |
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $clauses = [ |
||||
'where' => [], |
||||
]; |
||||
|
||||
/** |
||||
* @param mixed ...$args |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function where(...$args) |
||||
{ |
||||
array_push($this->clauses['where'], $args); |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* @param mixed $payload |
||||
* |
||||
* @return bool |
||||
*/ |
||||
public function intercepted($payload) |
||||
{ |
||||
return (bool) $this->interceptWhereClause($payload); |
||||
} |
||||
|
||||
/** |
||||
* @param mixed $payload |
||||
* |
||||
* @return bool |
||||
*/ |
||||
protected function interceptWhereClause($payload) |
||||
{ |
||||
foreach ($this->clauses['where'] as $item) { |
||||
list($key, $value) = $item; |
||||
if (isset($payload[$key]) && $payload[$key] !== $value) { |
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
<?php |
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel; |
||||
|
||||
use ByteDance\Kernel\Support\Collection; |
||||
|
||||
/** |
||||
* Class Config. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class Config extends Collection |
||||
{ |
||||
} |
@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
|
||||
<?php |
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel\Contracts; |
||||
|
||||
use Psr\Http\Message\RequestInterface; |
||||
|
||||
/** |
||||
* Interface AuthorizerAccessToken. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
interface AccessTokenInterface |
||||
{ |
||||
/** |
||||
* @return array |
||||
*/ |
||||
public function getToken(): array; |
||||
|
||||
/** |
||||
* @return \ByteDance\Kernel\Contracts\AccessTokenInterface |
||||
*/ |
||||
public function refresh(): self; |
||||
|
||||
/** |
||||
* @param \Psr\Http\Message\RequestInterface $request |
||||
* @param array $requestOptions |
||||
* |
||||
* @return \Psr\Http\Message\RequestInterface |
||||
*/ |
||||
public function applyToRequest(RequestInterface $request, array $requestOptions = []): RequestInterface; |
||||
} |
@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
|
||||
<?php |
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel\Contracts; |
||||
|
||||
use ArrayAccess; |
||||
|
||||
/** |
||||
* Interface Arrayable. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
interface Arrayable extends ArrayAccess |
||||
{ |
||||
/** |
||||
* Get the instance as an array. |
||||
* |
||||
* @return array |
||||
*/ |
||||
public function toArray(); |
||||
} |
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel\Contracts; |
||||
|
||||
/** |
||||
* Interface EventHandlerInterface. |
||||
* |
||||
* @author mingyoung <mingyoungcheung@gmail.com> |
||||
*/ |
||||
interface EventHandlerInterface |
||||
{ |
||||
/** |
||||
* @param mixed $payload |
||||
*/ |
||||
public function handle($payload = null); |
||||
} |
@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel\Decorators; |
||||
|
||||
/** |
||||
* Class FinallyResult. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class FinallyResult |
||||
{ |
||||
/** |
||||
* @var mixed |
||||
*/ |
||||
public $content; |
||||
|
||||
/** |
||||
* FinallyResult constructor. |
||||
* |
||||
* @param mixed $content |
||||
*/ |
||||
public function __construct($content) |
||||
{ |
||||
$this->content = $content; |
||||
} |
||||
} |
@ -0,0 +1,34 @@
@@ -0,0 +1,34 @@
|
||||
<?php |
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel\Decorators; |
||||
|
||||
/** |
||||
* Class TerminateResult. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class TerminateResult |
||||
{ |
||||
/** |
||||
* @var mixed |
||||
*/ |
||||
public $content; |
||||
|
||||
/** |
||||
* FinallyResult constructor. |
||||
* |
||||
* @param mixed $content |
||||
*/ |
||||
public function __construct($content) |
||||
{ |
||||
$this->content = $content; |
||||
} |
||||
} |
@ -0,0 +1,113 @@
@@ -0,0 +1,113 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel; |
||||
|
||||
use ByteDance\Kernel\Exceptions\RuntimeException; |
||||
use ByteDance\Kernel\Support\AES; |
||||
use Throwable; |
||||
use function ByteDance\Kernel\Support\str_random; |
||||
|
||||
/** |
||||
* Class Encryptor. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class Encryptor |
||||
{ |
||||
const ERROR_INVALID_SIGNATURE = -40001; // Signature verification failed |
||||
const ERROR_CALC_SIGNATURE = -40003; // Calculating the signature failed |
||||
const ERROR_INVALID_AES_KEY = -40004; // Invalid AESKey |
||||
const ERROR_INVALID_APP_ID = -40005; // Check AppID failed |
||||
const ERROR_ENCRYPT_AES = -40006; // AES EncryptionInterface failed |
||||
const ERROR_DECRYPT_AES = -40007; // AES decryption failed |
||||
const ERROR_BASE64_ENCODE = -40009; // Base64 encoding failed |
||||
const ERROR_BASE64_DECODE = -40010; // Base64 decoding failed |
||||
const ILLEGAL_BUFFER = -41003; // Illegal buffer |
||||
|
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $aesKey; |
||||
|
||||
/** |
||||
* Block size. |
||||
* |
||||
* @var int |
||||
*/ |
||||
protected $blockSize = 32; |
||||
|
||||
/** |
||||
* Constructor. |
||||
* |
||||
* @param string $appId |
||||
* @param string|null $token |
||||
* @param string|null $aesKey |
||||
*/ |
||||
public function __construct(string $aesKey = null) |
||||
{ |
||||
$this->aesKey = base64_decode($aesKey.'=', true); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Get SHA1. |
||||
* |
||||
* @return string |
||||
* |
||||
* @throws self |
||||
*/ |
||||
public function signature(): string |
||||
{ |
||||
$array = func_get_args(); |
||||
sort($array, SORT_STRING); |
||||
|
||||
return sha1(implode($array)); |
||||
} |
||||
|
||||
/** |
||||
* PKCS#7 pad. |
||||
* |
||||
* @param string $text |
||||
* @param int $blockSize |
||||
* |
||||
* @return string |
||||
* |
||||
* @throws \ByteDance\Kernel\Exceptions\RuntimeException |
||||
*/ |
||||
public function pkcs7Pad(string $text, int $blockSize): string |
||||
{ |
||||
if ($blockSize > 256) { |
||||
throw new RuntimeException('$blockSize may not be more than 256'); |
||||
} |
||||
$padding = $blockSize - (strlen($text) % $blockSize); |
||||
$pattern = chr($padding); |
||||
|
||||
return $text.str_repeat($pattern, $padding); |
||||
} |
||||
|
||||
/** |
||||
* PKCS#7 unpad. |
||||
* |
||||
* @param string $text |
||||
* |
||||
* @return string |
||||
*/ |
||||
public function pkcs7Unpad(string $text): string |
||||
{ |
||||
$pad = ord(substr($text, -1)); |
||||
if ($pad < 1 || $pad > $this->blockSize) { |
||||
$pad = 0; |
||||
} |
||||
|
||||
return substr($text, 0, (strlen($text) - $pad)); |
||||
} |
||||
} |
@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel\Exceptions; |
||||
|
||||
/** |
||||
* Class BadRequestException. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class BadRequestException extends Exception |
||||
{ |
||||
} |
@ -0,0 +1,16 @@
@@ -0,0 +1,16 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel\Exceptions; |
||||
|
||||
class DecryptException extends Exception |
||||
{ |
||||
} |
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel\Exceptions; |
||||
|
||||
use Exception as BaseException; |
||||
|
||||
/** |
||||
* Class Exception. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class Exception extends BaseException |
||||
{ |
||||
} |
@ -0,0 +1,52 @@
@@ -0,0 +1,52 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel\Exceptions; |
||||
|
||||
use Psr\Http\Message\ResponseInterface; |
||||
|
||||
/** |
||||
* Class HttpException. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class HttpException extends Exception |
||||
{ |
||||
/** |
||||
* @var \Psr\Http\Message\ResponseInterface|null |
||||
*/ |
||||
public $response; |
||||
|
||||
/** |
||||
* @var \Psr\Http\Message\ResponseInterface|\ByteDance\Kernel\Support\Collection|array|object|string |
||||
*/ |
||||
public $formattedResponse; |
||||
|
||||
/** |
||||
* HttpException constructor. |
||||
* |
||||
* @param string $message |
||||
* @param \Psr\Http\Message\ResponseInterface|null $response |
||||
* @param null $formattedResponse |
||||
* @param int|null $code |
||||
*/ |
||||
public function __construct($message, ResponseInterface $response = null, $formattedResponse = null, $code = null) |
||||
{ |
||||
parent::__construct($message, $code); |
||||
|
||||
$this->response = $response; |
||||
$this->formattedResponse = $formattedResponse; |
||||
|
||||
if ($response) { |
||||
$response->getBody()->rewind(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel\Exceptions; |
||||
|
||||
/** |
||||
* Class InvalidArgumentException. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class InvalidArgumentException extends Exception |
||||
{ |
||||
} |
@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel\Exceptions; |
||||
|
||||
/** |
||||
* Class InvalidConfigException. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class InvalidConfigException extends Exception |
||||
{ |
||||
} |
@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel\Exceptions; |
||||
|
||||
/** |
||||
* Class RuntimeException. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class RuntimeException extends Exception |
||||
{ |
||||
} |
@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel\Exceptions; |
||||
|
||||
/** |
||||
* Class InvalidConfigException. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class UnboundServiceException extends Exception |
||||
{ |
||||
} |
@ -0,0 +1,53 @@
@@ -0,0 +1,53 @@
|
||||
<?php |
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel; |
||||
|
||||
use ByteDance\Kernel\Contracts\Arrayable; |
||||
use ByteDance\Kernel\Exceptions\RuntimeException; |
||||
use ByteDance\Kernel\Support\Arr; |
||||
use ByteDance\Kernel\Support\Collection; |
||||
|
||||
function data_get($data, $key, $default = null) |
||||
{ |
||||
switch (true) { |
||||
case is_array($data): |
||||
return Arr::get($data, $key, $default); |
||||
case $data instanceof Collection: |
||||
return $data->get($key, $default); |
||||
case $data instanceof Arrayable: |
||||
return Arr::get($data->toArray(), $key, $default); |
||||
case $data instanceof \ArrayIterator: |
||||
return $data->getArrayCopy()[$key] ?? $default; |
||||
case $data instanceof \ArrayAccess: |
||||
return $data[$key] ?? $default; |
||||
case $data instanceof \IteratorAggregate && $data->getIterator() instanceof \ArrayIterator: |
||||
return $data->getIterator()->getArrayCopy()[$key] ?? $default; |
||||
default: |
||||
throw new RuntimeException(sprintf('Can\'t access data with key "%s"', $key)); |
||||
} |
||||
} |
||||
function data_to_array($data) |
||||
{ |
||||
switch (true) { |
||||
case is_array($data): |
||||
return $data; |
||||
case $data instanceof Collection: |
||||
return $data->all(); |
||||
case $data instanceof Arrayable: |
||||
return $data->toArray(); |
||||
case $data instanceof \IteratorAggregate && $data->getIterator() instanceof \ArrayIterator: |
||||
return $data->getIterator()->getArrayCopy(); |
||||
case $data instanceof \ArrayIterator: |
||||
return $data->getArrayCopy(); |
||||
default: |
||||
throw new RuntimeException(sprintf('Can\'t transform data to array')); |
||||
} |
||||
} |
@ -0,0 +1,116 @@
@@ -0,0 +1,116 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel\Http; |
||||
|
||||
use GuzzleHttp\Psr7\Response as GuzzleResponse; |
||||
use ByteDance\Kernel\Support\Collection; |
||||
use Psr\Http\Message\ResponseInterface; |
||||
|
||||
/** |
||||
* Class Response. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class Response extends GuzzleResponse |
||||
{ |
||||
/** |
||||
* @return string |
||||
*/ |
||||
public function getBodyContents() |
||||
{ |
||||
$this->getBody()->rewind(); |
||||
$contents = $this->getBody()->getContents(); |
||||
$this->getBody()->rewind(); |
||||
|
||||
return $contents; |
||||
} |
||||
|
||||
/** |
||||
* @param \Psr\Http\Message\ResponseInterface $response |
||||
* |
||||
* @return \ByteDance\Kernel\Http\Response |
||||
*/ |
||||
public static function buildFromPsrResponse(ResponseInterface $response) |
||||
{ |
||||
return new static( |
||||
$response->getStatusCode(), |
||||
$response->getHeaders(), |
||||
$response->getBody(), |
||||
$response->getProtocolVersion(), |
||||
$response->getReasonPhrase() |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Build to json. |
||||
* |
||||
* @return string |
||||
*/ |
||||
public function toJson() |
||||
{ |
||||
return json_encode($this->toArray()); |
||||
} |
||||
|
||||
/** |
||||
* Build to array. |
||||
* |
||||
* @return array |
||||
*/ |
||||
public function toArray() |
||||
{ |
||||
$content = $this->removeControlCharacters($this->getBodyContents()); |
||||
|
||||
$array = json_decode($content, true, 512, JSON_BIGINT_AS_STRING); |
||||
|
||||
if (JSON_ERROR_NONE === json_last_error()) { |
||||
return (array) $array; |
||||
} |
||||
|
||||
return []; |
||||
} |
||||
|
||||
/** |
||||
* Get collection data. |
||||
* |
||||
* @return \ByteDance\Kernel\Support\Collection |
||||
*/ |
||||
public function toCollection() |
||||
{ |
||||
return new Collection($this->toArray()); |
||||
} |
||||
|
||||
/** |
||||
* @return object |
||||
*/ |
||||
public function toObject() |
||||
{ |
||||
return json_decode($this->toJson()); |
||||
} |
||||
|
||||
/** |
||||
* @return bool|string |
||||
*/ |
||||
public function __toString() |
||||
{ |
||||
return $this->getBodyContents(); |
||||
} |
||||
|
||||
/** |
||||
* @param string $content |
||||
* |
||||
* @return string |
||||
*/ |
||||
protected function removeControlCharacters(string $content) |
||||
{ |
||||
return \preg_replace('/[\x00-\x1F\x80-\x9F]/u', '', $content); |
||||
} |
||||
} |
@ -0,0 +1,546 @@
@@ -0,0 +1,546 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel\Log; |
||||
|
||||
use Monolog\Formatter\LineFormatter; |
||||
use Monolog\Handler\ErrorLogHandler; |
||||
use Monolog\Handler\HandlerInterface; |
||||
use Monolog\Handler\RotatingFileHandler; |
||||
use Monolog\Handler\SlackWebhookHandler; |
||||
use Monolog\Handler\StreamHandler; |
||||
use Monolog\Handler\SyslogHandler; |
||||
use Monolog\Logger as Monolog; |
||||
use ByteDance\Kernel\ServiceContainer; |
||||
use Psr\Log\LoggerInterface; |
||||
|
||||
/** |
||||
* Class LogManager. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class LogManager implements LoggerInterface |
||||
{ |
||||
/** |
||||
* @var \ByteDance\Kernel\ServiceContainer |
||||
*/ |
||||
protected $app; |
||||
|
||||
/** |
||||
* The array of resolved channels. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected $channels = []; |
||||
|
||||
/** |
||||
* The registered custom driver creators. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected $customCreators = []; |
||||
|
||||
/** |
||||
* The Log levels. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected $levels = [ |
||||
'debug' => Monolog::DEBUG, |
||||
'info' => Monolog::INFO, |
||||
'notice' => Monolog::NOTICE, |
||||
'warning' => Monolog::WARNING, |
||||
'error' => Monolog::ERROR, |
||||
'critical' => Monolog::CRITICAL, |
||||
'alert' => Monolog::ALERT, |
||||
'emergency' => Monolog::EMERGENCY, |
||||
]; |
||||
|
||||
/** |
||||
* LogManager constructor. |
||||
* |
||||
* @param \ByteDance\Kernel\ServiceContainer $app |
||||
*/ |
||||
public function __construct(ServiceContainer $app) |
||||
{ |
||||
$this->app = $app; |
||||
} |
||||
|
||||
/** |
||||
* Create a new, on-demand aggregate logger instance. |
||||
* |
||||
* @param array $channels |
||||
* @param string|null $channel |
||||
* |
||||
* @return \Psr\Log\LoggerInterface |
||||
*/ |
||||
public function stack(array $channels, $channel = null) |
||||
{ |
||||
return $this->createStackDriver(compact('channels', 'channel')); |
||||
} |
||||
|
||||
/** |
||||
* Get a log channel instance. |
||||
* |
||||
* @param string|null $channel |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public function channel($channel = null) |
||||
{ |
||||
return $this->get($channel); |
||||
} |
||||
|
||||
/** |
||||
* Get a log driver instance. |
||||
* |
||||
* @param string|null $driver |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public function driver($driver = null) |
||||
{ |
||||
return $this->get($driver ?? $this->getDefaultDriver()); |
||||
} |
||||
|
||||
/** |
||||
* Attempt to get the log from the local cache. |
||||
* |
||||
* @param string $name |
||||
* |
||||
* @return \Psr\Log\LoggerInterface |
||||
*/ |
||||
protected function get($name) |
||||
{ |
||||
try { |
||||
return $this->channels[$name] ?? ($this->channels[$name] = $this->resolve($name)); |
||||
} catch (\Throwable $e) { |
||||
$logger = $this->createEmergencyLogger(); |
||||
|
||||
$logger->emergency('Unable to create configured logger. Using emergency logger.', [ |
||||
'exception' => $e, |
||||
]); |
||||
|
||||
return $logger; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Resolve the given log instance by name. |
||||
* |
||||
* @param string $name |
||||
* |
||||
* @throws \InvalidArgumentException |
||||
* |
||||
* @return \Psr\Log\LoggerInterface |
||||
*/ |
||||
protected function resolve($name) |
||||
{ |
||||
$config = $this->app['config']->get(\sprintf('log.channels.%s', $name)); |
||||
|
||||
if (is_null($config)) { |
||||
throw new \InvalidArgumentException(\sprintf('Log [%s] is not defined.', $name)); |
||||
} |
||||
|
||||
if (isset($this->customCreators[$config['driver']])) { |
||||
return $this->callCustomCreator($config); |
||||
} |
||||
|
||||
$driverMethod = 'create'.ucfirst($config['driver']).'Driver'; |
||||
|
||||
if (method_exists($this, $driverMethod)) { |
||||
return $this->{$driverMethod}($config); |
||||
} |
||||
|
||||
throw new \InvalidArgumentException(\sprintf('Driver [%s] is not supported.', $config['driver'])); |
||||
} |
||||
|
||||
/** |
||||
* Create an emergency log handler to avoid white screens of death. |
||||
* |
||||
* @return \Monolog\Logger |
||||
* @throws \Exception |
||||
*/ |
||||
protected function createEmergencyLogger() |
||||
{ |
||||
return new Monolog('EasyWeChat', $this->prepareHandlers([new StreamHandler( |
||||
\sys_get_temp_dir().'/easywechat/easywechat.log', $this->level(['level' => 'debug']) |
||||
)])); |
||||
} |
||||
|
||||
/** |
||||
* Call a custom driver creator. |
||||
* |
||||
* @param array $config |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
protected function callCustomCreator(array $config) |
||||
{ |
||||
return $this->customCreators[$config['driver']]($this->app, $config); |
||||
} |
||||
|
||||
/** |
||||
* Create an aggregate log driver instance. |
||||
* |
||||
* @param array $config |
||||
* |
||||
* @return \Monolog\Logger |
||||
*/ |
||||
protected function createStackDriver(array $config) |
||||
{ |
||||
$handlers = []; |
||||
|
||||
foreach ($config['channels'] ?? [] as $channel) { |
||||
$handlers = \array_merge($handlers, $this->channel($channel)->getHandlers()); |
||||
} |
||||
|
||||
return new Monolog($this->parseChannel($config), $handlers); |
||||
} |
||||
|
||||
/** |
||||
* Create an instance of the single file log driver. |
||||
* |
||||
* @param array $config |
||||
* |
||||
* @return \Psr\Log\LoggerInterface |
||||
* @throws \Exception |
||||
*/ |
||||
protected function createSingleDriver(array $config) |
||||
{ |
||||
return new Monolog($this->parseChannel($config), [ |
||||
$this->prepareHandler( |
||||
new StreamHandler($config['path'], $this->level($config)) |
||||
), |
||||
]); |
||||
} |
||||
|
||||
/** |
||||
* Create an instance of the daily file log driver. |
||||
* |
||||
* @param array $config |
||||
* |
||||
* @return \Psr\Log\LoggerInterface |
||||
*/ |
||||
protected function createDailyDriver(array $config) |
||||
{ |
||||
return new Monolog($this->parseChannel($config), [ |
||||
$this->prepareHandler(new RotatingFileHandler( |
||||
$config['path'], $config['days'] ?? 7, $this->level($config) |
||||
)), |
||||
]); |
||||
} |
||||
|
||||
/** |
||||
* Create an instance of the Slack log driver. |
||||
* |
||||
* @param array $config |
||||
* |
||||
* @return \Psr\Log\LoggerInterface |
||||
*/ |
||||
protected function createSlackDriver(array $config) |
||||
{ |
||||
return new Monolog($this->parseChannel($config), [ |
||||
$this->prepareHandler(new SlackWebhookHandler( |
||||
$config['url'], |
||||
$config['channel'] ?? null, |
||||
$config['username'] ?? 'EasyWeChat', |
||||
$config['attachment'] ?? true, |
||||
$config['emoji'] ?? ':boom:', |
||||
$config['short'] ?? false, |
||||
$config['context'] ?? true, |
||||
$this->level($config) |
||||
)), |
||||
]); |
||||
} |
||||
|
||||
/** |
||||
* Create an instance of the syslog log driver. |
||||
* |
||||
* @param array $config |
||||
* |
||||
* @return \Psr\Log\LoggerInterface |
||||
*/ |
||||
protected function createSyslogDriver(array $config) |
||||
{ |
||||
return new Monolog($this->parseChannel($config), [ |
||||
$this->prepareHandler(new SyslogHandler( |
||||
'EasyWeChat', $config['facility'] ?? LOG_USER, $this->level($config)) |
||||
), |
||||
]); |
||||
} |
||||
|
||||
/** |
||||
* Create an instance of the "error log" log driver. |
||||
* |
||||
* @param array $config |
||||
* |
||||
* @return \Psr\Log\LoggerInterface |
||||
*/ |
||||
protected function createErrorlogDriver(array $config) |
||||
{ |
||||
return new Monolog($this->parseChannel($config), [ |
||||
$this->prepareHandler(new ErrorLogHandler( |
||||
$config['type'] ?? ErrorLogHandler::OPERATING_SYSTEM, $this->level($config)) |
||||
), |
||||
]); |
||||
} |
||||
|
||||
/** |
||||
* Prepare the handlers for usage by Monolog. |
||||
* |
||||
* @param array $handlers |
||||
* |
||||
* @return array |
||||
*/ |
||||
protected function prepareHandlers(array $handlers) |
||||
{ |
||||
foreach ($handlers as $key => $handler) { |
||||
$handlers[$key] = $this->prepareHandler($handler); |
||||
} |
||||
|
||||
return $handlers; |
||||
} |
||||
|
||||
/** |
||||
* Prepare the handler for usage by Monolog. |
||||
* |
||||
* @param \Monolog\Handler\HandlerInterface $handler |
||||
* |
||||
* @return \Monolog\Handler\HandlerInterface |
||||
*/ |
||||
protected function prepareHandler(HandlerInterface $handler) |
||||
{ |
||||
return $handler->setFormatter($this->formatter()); |
||||
} |
||||
|
||||
/** |
||||
* Get a Monolog formatter instance. |
||||
* |
||||
* @return \Monolog\Formatter\FormatterInterface |
||||
*/ |
||||
protected function formatter() |
||||
{ |
||||
$formatter = new LineFormatter(null, null, true, true); |
||||
$formatter->includeStacktraces(); |
||||
|
||||
return $formatter; |
||||
} |
||||
|
||||
/** |
||||
* Extract the log channel from the given configuration. |
||||
* |
||||
* @param array $config |
||||
* |
||||
* @return string |
||||
*/ |
||||
protected function parseChannel(array $config) |
||||
{ |
||||
return $config['name'] ?? null; |
||||
} |
||||
|
||||
/** |
||||
* Parse the string level into a Monolog constant. |
||||
* |
||||
* @param array $config |
||||
* |
||||
* @throws \InvalidArgumentException |
||||
* |
||||
* @return int |
||||
*/ |
||||
protected function level(array $config) |
||||
{ |
||||
$level = $config['level'] ?? 'debug'; |
||||
|
||||
if (isset($this->levels[$level])) { |
||||
return $this->levels[$level]; |
||||
} |
||||
|
||||
throw new \InvalidArgumentException('Invalid log level.'); |
||||
} |
||||
|
||||
/** |
||||
* Get the default log driver name. |
||||
* |
||||
* @return string |
||||
*/ |
||||
public function getDefaultDriver() |
||||
{ |
||||
return $this->app['config']['log.default']; |
||||
} |
||||
|
||||
/** |
||||
* Set the default log driver name. |
||||
* |
||||
* @param string $name |
||||
*/ |
||||
public function setDefaultDriver($name) |
||||
{ |
||||
$this->app['config']['log.default'] = $name; |
||||
} |
||||
|
||||
/** |
||||
* Register a custom driver creator Closure. |
||||
* |
||||
* @param string $driver |
||||
* @param \Closure $callback |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function extend($driver, \Closure $callback) |
||||
{ |
||||
$this->customCreators[$driver] = $callback->bindTo($this, $this); |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* System is unusable. |
||||
* |
||||
* @param string $message |
||||
* @param array $context |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public function emergency($message, array $context = []) |
||||
{ |
||||
return $this->driver()->emergency($message, $context); |
||||
} |
||||
|
||||
/** |
||||
* Action must be taken immediately. |
||||
* |
||||
* Example: Entire website down, database unavailable, etc. This should |
||||
* trigger the SMS alerts and wake you up. |
||||
* |
||||
* @param string $message |
||||
* @param array $context |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public function alert($message, array $context = []) |
||||
{ |
||||
return $this->driver()->alert($message, $context); |
||||
} |
||||
|
||||
/** |
||||
* Critical conditions. |
||||
* |
||||
* Example: Application component unavailable, unexpected exception. |
||||
* |
||||
* @param string $message |
||||
* @param array $context |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public function critical($message, array $context = []) |
||||
{ |
||||
return $this->driver()->critical($message, $context); |
||||
} |
||||
|
||||
/** |
||||
* Runtime errors that do not require immediate action but should typically |
||||
* be logged and monitored. |
||||
* |
||||
* @param string $message |
||||
* @param array $context |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public function error($message, array $context = []) |
||||
{ |
||||
return $this->driver()->error($message, $context); |
||||
} |
||||
|
||||
/** |
||||
* Exceptional occurrences that are not errors. |
||||
* |
||||
* Example: Use of deprecated APIs, poor use of an API, undesirable things |
||||
* that are not necessarily wrong. |
||||
* |
||||
* @param string $message |
||||
* @param array $context |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public function warning($message, array $context = []) |
||||
{ |
||||
return $this->driver()->warning($message, $context); |
||||
} |
||||
|
||||
/** |
||||
* Normal but significant events. |
||||
* |
||||
* @param string $message |
||||
* @param array $context |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public function notice($message, array $context = []) |
||||
{ |
||||
return $this->driver()->notice($message, $context); |
||||
} |
||||
|
||||
/** |
||||
* Interesting events. |
||||
* |
||||
* Example: User logs in, SQL logs. |
||||
* |
||||
* @param string $message |
||||
* @param array $context |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public function info($message, array $context = []) |
||||
{ |
||||
return $this->driver()->info($message, $context); |
||||
} |
||||
|
||||
/** |
||||
* Detailed debug information. |
||||
* |
||||
* @param string $message |
||||
* @param array $context |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public function debug($message, array $context = []) |
||||
{ |
||||
return $this->driver()->debug($message, $context); |
||||
} |
||||
|
||||
/** |
||||
* Logs with an arbitrary level. |
||||
* |
||||
* @param mixed $level |
||||
* @param string $message |
||||
* @param array $context |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public function log($level, $message, array $context = []) |
||||
{ |
||||
return $this->driver()->log($level, $message, $context); |
||||
} |
||||
|
||||
/** |
||||
* Dynamically call the default driver instance. |
||||
* |
||||
* @param string $method |
||||
* @param array $parameters |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public function __call($method, $parameters) |
||||
{ |
||||
return $this->driver()->$method(...$parameters); |
||||
} |
||||
} |
@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel\Providers; |
||||
|
||||
use ByteDance\Kernel\Config; |
||||
use Pimple\Container; |
||||
use Pimple\ServiceProviderInterface; |
||||
|
||||
/** |
||||
* Class ConfigServiceProvider. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class ConfigServiceProvider implements ServiceProviderInterface |
||||
{ |
||||
/** |
||||
* Registers services on the given container. |
||||
* |
||||
* This method should only be used to configure services and parameters. |
||||
* It should not get services. |
||||
* |
||||
* @param Container $pimple A container instance |
||||
*/ |
||||
public function register(Container $pimple) |
||||
{ |
||||
$pimple['config'] = function ($app) { |
||||
return new Config($app->getConfig()); |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel\Providers; |
||||
|
||||
use GuzzleHttp\Client; |
||||
use Pimple\Container; |
||||
use Pimple\ServiceProviderInterface; |
||||
|
||||
/** |
||||
* Class HttpClientServiceProvider. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class HttpClientServiceProvider implements ServiceProviderInterface |
||||
{ |
||||
/** |
||||
* Registers services on the given container. |
||||
* |
||||
* This method should only be used to configure services and parameters. |
||||
* It should not get services. |
||||
* |
||||
* @param Container $pimple A container instance |
||||
*/ |
||||
public function register(Container $pimple) |
||||
{ |
||||
$pimple['http_client'] = function ($app) { |
||||
return new Client($app['config']->get('http', [])); |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,79 @@
@@ -0,0 +1,79 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel\Providers; |
||||
|
||||
use ByteDance\Kernel\Log\LogManager; |
||||
use Pimple\Container; |
||||
use Pimple\ServiceProviderInterface; |
||||
|
||||
/** |
||||
* Class LoggingServiceProvider. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class LogServiceProvider implements ServiceProviderInterface |
||||
{ |
||||
/** |
||||
* Registers services on the given container. |
||||
* |
||||
* This method should only be used to configure services and parameters. |
||||
* It should not get services. |
||||
* |
||||
* @param Container $pimple A container instance |
||||
*/ |
||||
public function register(Container $pimple) |
||||
{ |
||||
$pimple['logger'] = $pimple['log'] = function ($app) { |
||||
$config = $this->formatLogConfig($app); |
||||
|
||||
if (!empty($config)) { |
||||
$app->rebind('config', $app['config']->merge($config)); |
||||
} |
||||
|
||||
return new LogManager($app); |
||||
}; |
||||
} |
||||
|
||||
public function formatLogConfig($app) |
||||
{ |
||||
if (!empty($app['config']->get('log.channels'))) { |
||||
return $app['config']->get('log'); |
||||
} |
||||
|
||||
if (empty($app['config']->get('log'))) { |
||||
return [ |
||||
'log' => [ |
||||
'default' => 'errorlog', |
||||
'channels' => [ |
||||
'errorlog' => [ |
||||
'driver' => 'errorlog', |
||||
'level' => 'debug', |
||||
], |
||||
], |
||||
], |
||||
]; |
||||
} |
||||
|
||||
return [ |
||||
'log' => [ |
||||
'default' => 'single', |
||||
'channels' => [ |
||||
'single' => [ |
||||
'driver' => 'single', |
||||
'path' => $app['config']->get('log.file') ?: \sys_get_temp_dir().'/logs/easywechat.log', |
||||
'level' => $app['config']->get('log.level', 'debug'), |
||||
], |
||||
], |
||||
], |
||||
]; |
||||
} |
||||
} |
@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel\Providers; |
||||
|
||||
use Pimple\Container; |
||||
use Pimple\ServiceProviderInterface; |
||||
use Symfony\Component\HttpFoundation\Request; |
||||
|
||||
/** |
||||
* Class RequestServiceProvider. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class RequestServiceProvider implements ServiceProviderInterface |
||||
{ |
||||
/** |
||||
* Registers services on the given container. |
||||
* |
||||
* This method should only be used to configure services and parameters. |
||||
* It should not get services. |
||||
* |
||||
* @param Container $pimple A container instance |
||||
*/ |
||||
public function register(Container $pimple) |
||||
{ |
||||
$pimple['request'] = function () { |
||||
return Request::createFromGlobals(); |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,144 @@
@@ -0,0 +1,144 @@
|
||||
<?php |
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel; |
||||
|
||||
use ByteDance\Kernel\Providers\ConfigServiceProvider; |
||||
use ByteDance\Kernel\Providers\HttpClientServiceProvider; |
||||
use ByteDance\Kernel\Providers\LogServiceProvider; |
||||
use ByteDance\Kernel\Providers\RequestServiceProvider; |
||||
use Pimple\Container; |
||||
|
||||
/** |
||||
* Class ServiceContainer. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
* |
||||
* @property \ByteDance\Kernel\Config $config |
||||
* @property \Symfony\Component\HttpFoundation\Request $request |
||||
* @property \GuzzleHttp\Client $http_client |
||||
* @property \Monolog\Logger $logger |
||||
*/ |
||||
class ServiceContainer extends Container |
||||
{ |
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $id; |
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $providers = []; |
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $defaultConfig = []; |
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $userConfig = []; |
||||
|
||||
/** |
||||
* Constructor. |
||||
* |
||||
* @param array $config |
||||
* @param array $prepends |
||||
* @param string|null $id |
||||
*/ |
||||
public function __construct(array $config = [], array $prepends = [], string $id = null) |
||||
{ |
||||
$this->registerProviders($this->getProviders()); |
||||
parent::__construct($prepends); |
||||
$this->userConfig = $config; |
||||
$this->id = $id; |
||||
} |
||||
|
||||
/** |
||||
* @return string |
||||
*/ |
||||
public function getId() |
||||
{ |
||||
return $this->id ?? $this->id = md5(json_encode($this->userConfig)); |
||||
} |
||||
|
||||
/** |
||||
* @return array |
||||
*/ |
||||
public function getConfig() |
||||
{ |
||||
$base = [ |
||||
// http://docs.guzzlephp.org/en/stable/request-options.html |
||||
'http' => [ |
||||
'timeout' => 30.0, |
||||
'base_uri' => 'https://developer.toutiao.com/', |
||||
], |
||||
]; |
||||
|
||||
return array_replace_recursive($base, $this->defaultConfig, $this->userConfig); |
||||
} |
||||
|
||||
/** |
||||
* Return all providers. |
||||
* |
||||
* @return array |
||||
*/ |
||||
public function getProviders() |
||||
{ |
||||
return array_merge([ |
||||
ConfigServiceProvider::class, |
||||
LogServiceProvider::class, |
||||
RequestServiceProvider::class, |
||||
HttpClientServiceProvider::class, |
||||
], $this->providers); |
||||
} |
||||
|
||||
/** |
||||
* @param string $id |
||||
* @param mixed $value |
||||
*/ |
||||
public function rebind($id, $value) |
||||
{ |
||||
$this->offsetUnset($id); |
||||
$this->offsetSet($id, $value); |
||||
} |
||||
|
||||
/** |
||||
* Magic get access. |
||||
* |
||||
* @param string $id |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public function __get($id) |
||||
{ |
||||
return $this->offsetGet($id); |
||||
} |
||||
|
||||
/** |
||||
* Magic set access. |
||||
* |
||||
* @param string $id |
||||
* @param mixed $value |
||||
*/ |
||||
public function __set($id, $value) |
||||
{ |
||||
$this->offsetSet($id, $value); |
||||
} |
||||
|
||||
/** |
||||
* @param array $providers |
||||
*/ |
||||
public function registerProviders(array $providers) |
||||
{ |
||||
foreach ($providers as $provider) { |
||||
parent::register(new $provider()); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,85 @@
@@ -0,0 +1,85 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel\Support; |
||||
|
||||
/** |
||||
* Class AES. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class AES |
||||
{ |
||||
/** |
||||
* @param string $text |
||||
* @param string $key |
||||
* @param string $iv |
||||
* @param int $option |
||||
* |
||||
* @return string |
||||
*/ |
||||
public static function encrypt(string $text, string $key, string $iv, int $option = OPENSSL_RAW_DATA): string |
||||
{ |
||||
self::validateKey($key); |
||||
self::validateIv($iv); |
||||
|
||||
return openssl_encrypt($text, self::getMode($key), $key, $option, $iv); |
||||
} |
||||
|
||||
/** |
||||
* @param string $cipherText |
||||
* @param string $key |
||||
* @param string $iv |
||||
* @param int $option |
||||
* @param string|null $method |
||||
* |
||||
* @return string |
||||
*/ |
||||
public static function decrypt(string $cipherText, string $key, string $iv, int $option = OPENSSL_RAW_DATA, $method = null): string |
||||
{ |
||||
self::validateKey($key); |
||||
self::validateIv($iv); |
||||
|
||||
return openssl_decrypt($cipherText, $method ?: self::getMode($key), $key, $option, $iv); |
||||
} |
||||
|
||||
/** |
||||
* @param string $key |
||||
* |
||||
* @return string |
||||
*/ |
||||
public static function getMode($key) |
||||
{ |
||||
return 'aes-'.(8 * strlen($key)).'-cbc'; |
||||
} |
||||
|
||||
/** |
||||
* @param string $key |
||||
*/ |
||||
public static function validateKey(string $key) |
||||
{ |
||||
if (!in_array(strlen($key), [16, 24, 32], true)) { |
||||
throw new \InvalidArgumentException(sprintf('Key length must be 16, 24, or 32 bytes; got key len (%s).', strlen($key))); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @param string $iv |
||||
* |
||||
* @throws \InvalidArgumentException |
||||
*/ |
||||
public static function validateIv(string $iv) |
||||
{ |
||||
if (!empty($iv) && 16 !== strlen($iv)) { |
||||
throw new \InvalidArgumentException('IV length must be 16 bytes.'); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,466 @@
@@ -0,0 +1,466 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel\Support; |
||||
|
||||
/** |
||||
* Array helper from Illuminate\Support\Arr. |
||||
*/ |
||||
class Arr |
||||
{ |
||||
/** |
||||
* Add an element to an array using "dot" notation if it doesn't exist. |
||||
* |
||||
* @param array $array |
||||
* @param string $key |
||||
* @param mixed $value |
||||
* |
||||
* @return array |
||||
*/ |
||||
public static function add(array $array, $key, $value) |
||||
{ |
||||
if (is_null(static::get($array, $key))) { |
||||
static::set($array, $key, $value); |
||||
} |
||||
|
||||
return $array; |
||||
} |
||||
|
||||
/** |
||||
* Cross join the given arrays, returning all possible permutations. |
||||
* |
||||
* @param array ...$arrays |
||||
* |
||||
* @return array |
||||
*/ |
||||
public static function crossJoin(...$arrays) |
||||
{ |
||||
$results = [[]]; |
||||
|
||||
foreach ($arrays as $index => $array) { |
||||
$append = []; |
||||
|
||||
foreach ($results as $product) { |
||||
foreach ($array as $item) { |
||||
$product[$index] = $item; |
||||
|
||||
$append[] = $product; |
||||
} |
||||
} |
||||
|
||||
$results = $append; |
||||
} |
||||
|
||||
return $results; |
||||
} |
||||
|
||||
/** |
||||
* Divide an array into two arrays. One with keys and the other with values. |
||||
* |
||||
* @param array $array |
||||
* |
||||
* @return array |
||||
*/ |
||||
public static function divide(array $array) |
||||
{ |
||||
return [array_keys($array), array_values($array)]; |
||||
} |
||||
|
||||
/** |
||||
* Flatten a multi-dimensional associative array with dots. |
||||
* |
||||
* @param array $array |
||||
* @param string $prepend |
||||
* |
||||
* @return array |
||||
*/ |
||||
public static function dot(array $array, $prepend = '') |
||||
{ |
||||
$results = []; |
||||
|
||||
foreach ($array as $key => $value) { |
||||
if (is_array($value) && !empty($value)) { |
||||
$results = array_merge($results, static::dot($value, $prepend.$key.'.')); |
||||
} else { |
||||
$results[$prepend.$key] = $value; |
||||
} |
||||
} |
||||
|
||||
return $results; |
||||
} |
||||
|
||||
/** |
||||
* Get all of the given array except for a specified array of items. |
||||
* |
||||
* @param array $array |
||||
* @param array|string $keys |
||||
* |
||||
* @return array |
||||
*/ |
||||
public static function except(array $array, $keys) |
||||
{ |
||||
static::forget($array, $keys); |
||||
|
||||
return $array; |
||||
} |
||||
|
||||
/** |
||||
* Determine if the given key exists in the provided array. |
||||
* |
||||
* @param \ArrayAccess|array $array |
||||
* @param string|int $key |
||||
* |
||||
* @return bool |
||||
*/ |
||||
public static function exists(array $array, $key) |
||||
{ |
||||
return array_key_exists($key, $array); |
||||
} |
||||
|
||||
/** |
||||
* Return the first element in an array passing a given truth test. |
||||
* |
||||
* @param array $array |
||||
* @param callable|null $callback |
||||
* @param mixed $default |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public static function first(array $array, callable $callback = null, $default = null) |
||||
{ |
||||
if (is_null($callback)) { |
||||
if (empty($array)) { |
||||
return $default; |
||||
} |
||||
|
||||
foreach ($array as $item) { |
||||
return $item; |
||||
} |
||||
} |
||||
|
||||
foreach ($array as $key => $value) { |
||||
if (call_user_func($callback, $value, $key)) { |
||||
return $value; |
||||
} |
||||
} |
||||
|
||||
return $default; |
||||
} |
||||
|
||||
/** |
||||
* Return the last element in an array passing a given truth test. |
||||
* |
||||
* @param array $array |
||||
* @param callable|null $callback |
||||
* @param mixed $default |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public static function last(array $array, callable $callback = null, $default = null) |
||||
{ |
||||
if (is_null($callback)) { |
||||
return empty($array) ? $default : end($array); |
||||
} |
||||
|
||||
return static::first(array_reverse($array, true), $callback, $default); |
||||
} |
||||
|
||||
/** |
||||
* Flatten a multi-dimensional array into a single level. |
||||
* |
||||
* @param array $array |
||||
* @param int $depth |
||||
* |
||||
* @return array |
||||
*/ |
||||
public static function flatten(array $array, $depth = INF) |
||||
{ |
||||
return array_reduce($array, function ($result, $item) use ($depth) { |
||||
$item = $item instanceof Collection ? $item->all() : $item; |
||||
|
||||
if (!is_array($item)) { |
||||
return array_merge($result, [$item]); |
||||
} elseif (1 === $depth) { |
||||
return array_merge($result, array_values($item)); |
||||
} |
||||
|
||||
return array_merge($result, static::flatten($item, $depth - 1)); |
||||
}, []); |
||||
} |
||||
|
||||
/** |
||||
* Remove one or many array items from a given array using "dot" notation. |
||||
* |
||||
* @param array $array |
||||
* @param array|string $keys |
||||
*/ |
||||
public static function forget(array &$array, $keys) |
||||
{ |
||||
$original = &$array; |
||||
|
||||
$keys = (array) $keys; |
||||
|
||||
if (0 === count($keys)) { |
||||
return; |
||||
} |
||||
|
||||
foreach ($keys as $key) { |
||||
// if the exact key exists in the top-level, remove it |
||||
if (static::exists($array, $key)) { |
||||
unset($array[$key]); |
||||
|
||||
continue; |
||||
} |
||||
|
||||
$parts = explode('.', $key); |
||||
|
||||
// clean up before each pass |
||||
$array = &$original; |
||||
|
||||
while (count($parts) > 1) { |
||||
$part = array_shift($parts); |
||||
|
||||
if (isset($array[$part]) && is_array($array[$part])) { |
||||
$array = &$array[$part]; |
||||
} else { |
||||
continue 2; |
||||
} |
||||
} |
||||
|
||||
unset($array[array_shift($parts)]); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Get an item from an array using "dot" notation. |
||||
* |
||||
* @param \ArrayAccess|array $array |
||||
* @param string $key |
||||
* @param mixed $default |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public static function get(array $array, $key, $default = null) |
||||
{ |
||||
if (is_null($key)) { |
||||
return $array; |
||||
} |
||||
|
||||
if (static::exists($array, $key)) { |
||||
return $array[$key]; |
||||
} |
||||
|
||||
foreach (explode('.', $key) as $segment) { |
||||
if (static::exists($array, $segment)) { |
||||
$array = $array[$segment]; |
||||
} else { |
||||
return $default; |
||||
} |
||||
} |
||||
|
||||
return $array; |
||||
} |
||||
|
||||
/** |
||||
* Check if an item or items exist in an array using "dot" notation. |
||||
* |
||||
* @param \ArrayAccess|array $array |
||||
* @param string|array $keys |
||||
* |
||||
* @return bool |
||||
*/ |
||||
public static function has(array $array, $keys) |
||||
{ |
||||
if (is_null($keys)) { |
||||
return false; |
||||
} |
||||
|
||||
$keys = (array) $keys; |
||||
|
||||
if (empty($array)) { |
||||
return false; |
||||
} |
||||
|
||||
if ($keys === []) { |
||||
return false; |
||||
} |
||||
|
||||
foreach ($keys as $key) { |
||||
$subKeyArray = $array; |
||||
|
||||
if (static::exists($array, $key)) { |
||||
continue; |
||||
} |
||||
|
||||
foreach (explode('.', $key) as $segment) { |
||||
if (static::exists($subKeyArray, $segment)) { |
||||
$subKeyArray = $subKeyArray[$segment]; |
||||
} else { |
||||
return false; |
||||
} |
||||
} |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* Determines if an array is associative. |
||||
* |
||||
* An array is "associative" if it doesn't have sequential numerical keys beginning with zero. |
||||
* |
||||
* @param array $array |
||||
* |
||||
* @return bool |
||||
*/ |
||||
public static function isAssoc(array $array) |
||||
{ |
||||
$keys = array_keys($array); |
||||
|
||||
return array_keys($keys) !== $keys; |
||||
} |
||||
|
||||
/** |
||||
* Get a subset of the items from the given array. |
||||
* |
||||
* @param array $array |
||||
* @param array|string $keys |
||||
* |
||||
* @return array |
||||
*/ |
||||
public static function only(array $array, $keys) |
||||
{ |
||||
return array_intersect_key($array, array_flip((array) $keys)); |
||||
} |
||||
|
||||
/** |
||||
* Push an item onto the beginning of an array. |
||||
* |
||||
* @param array $array |
||||
* @param mixed $value |
||||
* @param mixed $key |
||||
* |
||||
* @return array |
||||
*/ |
||||
public static function prepend(array $array, $value, $key = null) |
||||
{ |
||||
if (is_null($key)) { |
||||
array_unshift($array, $value); |
||||
} else { |
||||
$array = [$key => $value] + $array; |
||||
} |
||||
|
||||
return $array; |
||||
} |
||||
|
||||
/** |
||||
* Get a value from the array, and remove it. |
||||
* |
||||
* @param array $array |
||||
* @param string $key |
||||
* @param mixed $default |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public static function pull(array &$array, $key, $default = null) |
||||
{ |
||||
$value = static::get($array, $key, $default); |
||||
|
||||
static::forget($array, $key); |
||||
|
||||
return $value; |
||||
} |
||||
|
||||
/** |
||||
* Get a 1 value from an array. |
||||
* |
||||
* @param array $array |
||||
* @param int|null $amount |
||||
* |
||||
* @throws \InvalidArgumentException |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public static function random(array $array, int $amount = null) |
||||
{ |
||||
if (is_null($amount)) { |
||||
return $array[array_rand($array)]; |
||||
} |
||||
|
||||
$keys = array_rand($array, $amount); |
||||
|
||||
$results = []; |
||||
|
||||
foreach ((array) $keys as $key) { |
||||
$results[] = $array[$key]; |
||||
} |
||||
|
||||
return $results; |
||||
} |
||||
|
||||
/** |
||||
* Set an array item to a given value using "dot" notation. |
||||
* |
||||
* If no key is given to the method, the entire array will be replaced. |
||||
* |
||||
* @param array $array |
||||
* @param string $key |
||||
* @param mixed $value |
||||
* |
||||
* @return array |
||||
*/ |
||||
public static function set(array &$array, string $key, $value) |
||||
{ |
||||
$keys = explode('.', $key); |
||||
|
||||
while (count($keys) > 1) { |
||||
$key = array_shift($keys); |
||||
|
||||
// If the key doesn't exist at this depth, we will just create an empty array |
||||
// to hold the next value, allowing us to create the arrays to hold final |
||||
// values at the correct depth. Then we'll keep digging into the array. |
||||
if (!isset($array[$key]) || !is_array($array[$key])) { |
||||
$array[$key] = []; |
||||
} |
||||
|
||||
$array = &$array[$key]; |
||||
} |
||||
|
||||
$array[array_shift($keys)] = $value; |
||||
|
||||
return $array; |
||||
} |
||||
|
||||
/** |
||||
* Filter the array using the given callback. |
||||
* |
||||
* @param array $array |
||||
* @param callable $callback |
||||
* |
||||
* @return array |
||||
*/ |
||||
public static function where(array $array, callable $callback) |
||||
{ |
||||
return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH); |
||||
} |
||||
|
||||
/** |
||||
* If the given value is not an array, wrap it in one. |
||||
* |
||||
* @param mixed $value |
||||
* |
||||
* @return array |
||||
*/ |
||||
public static function wrap($value) |
||||
{ |
||||
return !is_array($value) ? [$value] : $value; |
||||
} |
||||
} |
@ -0,0 +1,66 @@
@@ -0,0 +1,66 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel\Support; |
||||
|
||||
use ArrayAccess; |
||||
use ArrayIterator; |
||||
use IteratorAggregate; |
||||
use ByteDance\Kernel\Contracts\Arrayable; |
||||
|
||||
/** |
||||
* Class ArrayAccessible. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class ArrayAccessible implements ArrayAccess, IteratorAggregate, Arrayable |
||||
{ |
||||
private $array; |
||||
|
||||
public function __construct(array $array = []) |
||||
{ |
||||
$this->array = $array; |
||||
} |
||||
|
||||
public function offsetExists($offset) |
||||
{ |
||||
return array_key_exists($offset, $this->array); |
||||
} |
||||
|
||||
public function offsetGet($offset) |
||||
{ |
||||
return $this->array[$offset]; |
||||
} |
||||
|
||||
public function offsetSet($offset, $value) |
||||
{ |
||||
if (null === $offset) { |
||||
$this->array[] = $value; |
||||
} else { |
||||
$this->array[$offset] = $value; |
||||
} |
||||
} |
||||
|
||||
public function offsetUnset($offset) |
||||
{ |
||||
unset($this->array[$offset]); |
||||
} |
||||
|
||||
public function getIterator() |
||||
{ |
||||
return new ArrayIterator($this->array); |
||||
} |
||||
|
||||
public function toArray() |
||||
{ |
||||
return $this->array; |
||||
} |
||||
} |
@ -0,0 +1,421 @@
@@ -0,0 +1,421 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel\Support; |
||||
|
||||
use ArrayAccess; |
||||
use ArrayIterator; |
||||
use Countable; |
||||
use IteratorAggregate; |
||||
use JsonSerializable; |
||||
use ByteDance\Kernel\Contracts\Arrayable; |
||||
use Serializable; |
||||
|
||||
/** |
||||
* Class Collection. |
||||
*/ |
||||
class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable, Serializable, Arrayable |
||||
{ |
||||
/** |
||||
* The collection data. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected $items = []; |
||||
|
||||
/** |
||||
* set data. |
||||
* |
||||
* @param mixed $items |
||||
*/ |
||||
public function __construct(array $items = []) |
||||
{ |
||||
foreach ($items as $key => $value) { |
||||
$this->set($key, $value); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Return all items. |
||||
* |
||||
* @return array |
||||
*/ |
||||
public function all() |
||||
{ |
||||
return $this->items; |
||||
} |
||||
|
||||
/** |
||||
* Return specific items. |
||||
* |
||||
* @param array $keys |
||||
* |
||||
* @return \ByteDance\Kernel\Support\Collection |
||||
*/ |
||||
public function only(array $keys) |
||||
{ |
||||
$return = []; |
||||
|
||||
foreach ($keys as $key) { |
||||
$value = $this->get($key); |
||||
|
||||
if (!is_null($value)) { |
||||
$return[$key] = $value; |
||||
} |
||||
} |
||||
|
||||
return new static($return); |
||||
} |
||||
|
||||
/** |
||||
* Get all items except for those with the specified keys. |
||||
* |
||||
* @param mixed $keys |
||||
* |
||||
* @return static |
||||
*/ |
||||
public function except($keys) |
||||
{ |
||||
$keys = is_array($keys) ? $keys : func_get_args(); |
||||
|
||||
return new static(Arr::except($this->items, $keys)); |
||||
} |
||||
|
||||
/** |
||||
* Merge data. |
||||
* |
||||
* @param Collection|array $items |
||||
* |
||||
* @return \ByteDance\Kernel\Support\Collection |
||||
*/ |
||||
public function merge($items) |
||||
{ |
||||
$clone = new static($this->all()); |
||||
|
||||
foreach ($items as $key => $value) { |
||||
$clone->set($key, $value); |
||||
} |
||||
|
||||
return $clone; |
||||
} |
||||
|
||||
/** |
||||
* To determine Whether the specified element exists. |
||||
* |
||||
* @param string $key |
||||
* |
||||
* @return bool |
||||
*/ |
||||
public function has($key) |
||||
{ |
||||
return !is_null(Arr::get($this->items, $key)); |
||||
} |
||||
|
||||
/** |
||||
* Retrieve the first item. |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public function first() |
||||
{ |
||||
return reset($this->items); |
||||
} |
||||
|
||||
/** |
||||
* Retrieve the last item. |
||||
* |
||||
* @return bool |
||||
*/ |
||||
public function last() |
||||
{ |
||||
$end = end($this->items); |
||||
|
||||
reset($this->items); |
||||
|
||||
return $end; |
||||
} |
||||
|
||||
/** |
||||
* add the item value. |
||||
* |
||||
* @param string $key |
||||
* @param mixed $value |
||||
*/ |
||||
public function add($key, $value) |
||||
{ |
||||
Arr::set($this->items, $key, $value); |
||||
} |
||||
|
||||
/** |
||||
* Set the item value. |
||||
* |
||||
* @param string $key |
||||
* @param mixed $value |
||||
*/ |
||||
public function set($key, $value) |
||||
{ |
||||
Arr::set($this->items, $key, $value); |
||||
} |
||||
|
||||
/** |
||||
* Retrieve item from Collection. |
||||
* |
||||
* @param string $key |
||||
* @param mixed $default |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public function get($key, $default = null) |
||||
{ |
||||
return Arr::get($this->items, $key, $default); |
||||
} |
||||
|
||||
/** |
||||
* Remove item form Collection. |
||||
* |
||||
* @param string $key |
||||
*/ |
||||
public function forget($key) |
||||
{ |
||||
Arr::forget($this->items, $key); |
||||
} |
||||
|
||||
/** |
||||
* Build to array. |
||||
* |
||||
* @return array |
||||
*/ |
||||
public function toArray() |
||||
{ |
||||
return $this->all(); |
||||
} |
||||
|
||||
/** |
||||
* Build to json. |
||||
* |
||||
* @param int $option |
||||
* |
||||
* @return string |
||||
*/ |
||||
public function toJson($option = JSON_UNESCAPED_UNICODE) |
||||
{ |
||||
return json_encode($this->all(), $option); |
||||
} |
||||
|
||||
/** |
||||
* To string. |
||||
* |
||||
* @return string |
||||
*/ |
||||
public function __toString() |
||||
{ |
||||
return $this->toJson(); |
||||
} |
||||
|
||||
/** |
||||
* (PHP 5 >= 5.4.0)<br/> |
||||
* Specify data which should be serialized to JSON. |
||||
* |
||||
* @see http://php.net/manual/en/jsonserializable.jsonserialize.php |
||||
* |
||||
* @return mixed data which can be serialized by <b>json_encode</b>, |
||||
* which is a value of any type other than a resource |
||||
*/ |
||||
public function jsonSerialize() |
||||
{ |
||||
return $this->items; |
||||
} |
||||
|
||||
/** |
||||
* (PHP 5 >= 5.1.0)<br/> |
||||
* String representation of object. |
||||
* |
||||
* @see http://php.net/manual/en/serializable.serialize.php |
||||
* |
||||
* @return string the string representation of the object or null |
||||
*/ |
||||
public function serialize() |
||||
{ |
||||
return serialize($this->items); |
||||
} |
||||
|
||||
/** |
||||
* (PHP 5 >= 5.0.0)<br/> |
||||
* Retrieve an external iterator. |
||||
* |
||||
* @see http://php.net/manual/en/iteratoraggregate.getiterator.php |
||||
* |
||||
* @return \ArrayIterator An instance of an object implementing <b>Iterator</b> or |
||||
* <b>Traversable</b> |
||||
*/ |
||||
public function getIterator() |
||||
{ |
||||
return new ArrayIterator($this->items); |
||||
} |
||||
|
||||
/** |
||||
* (PHP 5 >= 5.1.0)<br/> |
||||
* Count elements of an object. |
||||
* |
||||
* @see http://php.net/manual/en/countable.count.php |
||||
* |
||||
* @return int the custom count as an integer. |
||||
* </p> |
||||
* <p> |
||||
* The return value is cast to an integer |
||||
*/ |
||||
public function count() |
||||
{ |
||||
return count($this->items); |
||||
} |
||||
|
||||
/** |
||||
* (PHP 5 >= 5.1.0)<br/> |
||||
* Constructs the object. |
||||
* |
||||
* @see http://php.net/manual/en/serializable.unserialize.php |
||||
* |
||||
* @param string $serialized <p> |
||||
* The string representation of the object. |
||||
* </p> |
||||
* |
||||
* @return mixed|void |
||||
*/ |
||||
public function unserialize($serialized) |
||||
{ |
||||
return $this->items = unserialize($serialized); |
||||
} |
||||
|
||||
/** |
||||
* Get a data by key. |
||||
* |
||||
* @param string $key |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public function __get($key) |
||||
{ |
||||
return $this->get($key); |
||||
} |
||||
|
||||
/** |
||||
* Assigns a value to the specified data. |
||||
* |
||||
* @param string $key |
||||
* @param mixed $value |
||||
*/ |
||||
public function __set($key, $value) |
||||
{ |
||||
$this->set($key, $value); |
||||
} |
||||
|
||||
/** |
||||
* Whether or not an data exists by key. |
||||
* |
||||
* @param string $key |
||||
* |
||||
* @return bool |
||||
*/ |
||||
public function __isset($key) |
||||
{ |
||||
return $this->has($key); |
||||
} |
||||
|
||||
/** |
||||
* Unset an data by key. |
||||
* |
||||
* @param string $key |
||||
*/ |
||||
public function __unset($key) |
||||
{ |
||||
$this->forget($key); |
||||
} |
||||
|
||||
/** |
||||
* var_export. |
||||
* |
||||
* @return array |
||||
*/ |
||||
public function __set_state() |
||||
{ |
||||
return $this->all(); |
||||
} |
||||
|
||||
/** |
||||
* (PHP 5 >= 5.0.0)<br/> |
||||
* Whether a offset exists. |
||||
* |
||||
* @see http://php.net/manual/en/arrayaccess.offsetexists.php |
||||
* |
||||
* @param mixed $offset <p> |
||||
* An offset to check for. |
||||
* </p> |
||||
* |
||||
* @return bool true on success or false on failure. |
||||
* The return value will be casted to boolean if non-boolean was returned |
||||
*/ |
||||
public function offsetExists($offset) |
||||
{ |
||||
return $this->has($offset); |
||||
} |
||||
|
||||
/** |
||||
* (PHP 5 >= 5.0.0)<br/> |
||||
* Offset to unset. |
||||
* |
||||
* @see http://php.net/manual/en/arrayaccess.offsetunset.php |
||||
* |
||||
* @param mixed $offset <p> |
||||
* The offset to unset. |
||||
* </p> |
||||
*/ |
||||
public function offsetUnset($offset) |
||||
{ |
||||
if ($this->offsetExists($offset)) { |
||||
$this->forget($offset); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* (PHP 5 >= 5.0.0)<br/> |
||||
* Offset to retrieve. |
||||
* |
||||
* @see http://php.net/manual/en/arrayaccess.offsetget.php |
||||
* |
||||
* @param mixed $offset <p> |
||||
* The offset to retrieve. |
||||
* </p> |
||||
* |
||||
* @return mixed Can return all value types |
||||
*/ |
||||
public function offsetGet($offset) |
||||
{ |
||||
return $this->offsetExists($offset) ? $this->get($offset) : null; |
||||
} |
||||
|
||||
/** |
||||
* (PHP 5 >= 5.0.0)<br/> |
||||
* Offset to set. |
||||
* |
||||
* @see http://php.net/manual/en/arrayaccess.offsetset.php |
||||
* |
||||
* @param mixed $offset <p> |
||||
* The offset to assign the value to. |
||||
* </p> |
||||
* @param mixed $value <p> |
||||
* The value to set. |
||||
* </p> |
||||
*/ |
||||
public function offsetSet($offset, $value) |
||||
{ |
||||
$this->set($offset, $value); |
||||
} |
||||
} |
@ -0,0 +1,133 @@
@@ -0,0 +1,133 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel\Support; |
||||
|
||||
use finfo; |
||||
|
||||
/** |
||||
* Class File. |
||||
*/ |
||||
class File |
||||
{ |
||||
/** |
||||
* MIME mapping. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected static $extensionMap = [ |
||||
'audio/wav' => '.wav', |
||||
'audio/x-ms-wma' => '.wma', |
||||
'video/x-ms-wmv' => '.wmv', |
||||
'video/mp4' => '.mp4', |
||||
'audio/mpeg' => '.mp3', |
||||
'audio/amr' => '.amr', |
||||
'application/vnd.rn-realmedia' => '.rm', |
||||
'audio/mid' => '.mid', |
||||
'image/bmp' => '.bmp', |
||||
'image/gif' => '.gif', |
||||
'image/png' => '.png', |
||||
'image/tiff' => '.tiff', |
||||
'image/jpeg' => '.jpg', |
||||
'application/pdf' => '.pdf', |
||||
|
||||
// 列举更多的文件 mime, 企业号是支持的,公众平台这边之后万一也更新了呢 |
||||
'application/msword' => '.doc', |
||||
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => '.docx', |
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => '.dotx', |
||||
'application/vnd.ms-word.document.macroEnabled.12' => '.docm', |
||||
'application/vnd.ms-word.template.macroEnabled.12' => '.dotm', |
||||
|
||||
'application/vnd.ms-excel' => '.xls', |
||||
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => '.xlsx', |
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => '.xltx', |
||||
'application/vnd.ms-excel.sheet.macroEnabled.12' => '.xlsm', |
||||
'application/vnd.ms-excel.template.macroEnabled.12' => '.xltm', |
||||
'application/vnd.ms-excel.addin.macroEnabled.12' => '.xlam', |
||||
'application/vnd.ms-excel.sheet.binary.macroEnabled.12' => '.xlsb', |
||||
|
||||
'application/vnd.ms-powerpoint' => '.ppt', |
||||
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation' => '.pptx', |
||||
'application/vnd.openxmlformats-officedocument.presentationml.template' => '.potx', |
||||
'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => '.ppsx', |
||||
'application/vnd.ms-powerpoint.addin.macroEnabled.12' => '.ppam', |
||||
]; |
||||
|
||||
/** |
||||
* File header signatures. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected static $signatures = [ |
||||
'ffd8ff' => '.jpg', |
||||
'424d' => '.bmp', |
||||
'47494638' => '.gif', |
||||
'2f55736572732f6f7665' => '.png', |
||||
'89504e47' => '.png', |
||||
'494433' => '.mp3', |
||||
'fffb' => '.mp3', |
||||
'fff3' => '.mp3', |
||||
'3026b2758e66cf11' => '.wma', |
||||
'52494646' => '.wav', |
||||
'57415645' => '.wav', |
||||
'41564920' => '.avi', |
||||
'000001ba' => '.mpg', |
||||
'000001b3' => '.mpg', |
||||
'2321414d52' => '.amr', |
||||
'25504446' => '.pdf', |
||||
]; |
||||
|
||||
/** |
||||
* Return steam extension. |
||||
* |
||||
* @param string $stream |
||||
* |
||||
* @return string|false |
||||
*/ |
||||
public static function getStreamExt($stream) |
||||
{ |
||||
try { |
||||
if (is_readable($stream)) { |
||||
$stream = file_get_contents($stream); |
||||
} |
||||
} catch (\Exception $e) { |
||||
} |
||||
|
||||
$fileInfo = new finfo(FILEINFO_MIME); |
||||
|
||||
$mime = strstr($fileInfo->buffer($stream), ';', true); |
||||
|
||||
return isset(self::$extensionMap[$mime]) ? self::$extensionMap[$mime] : self::getExtBySignature($stream); |
||||
} |
||||
|
||||
/** |
||||
* Get file extension by file header signature. |
||||
* |
||||
* @param string $stream |
||||
* |
||||
* @return string |
||||
*/ |
||||
public static function getExtBySignature($stream) |
||||
{ |
||||
$prefix = strval(bin2hex(mb_strcut($stream, 0, 10))); |
||||
|
||||
foreach (self::$signatures as $signature => $extension) { |
||||
if (0 === strpos($prefix, strval($signature))) { |
||||
return $extension; |
||||
} |
||||
} |
||||
|
||||
return ''; |
||||
} |
||||
} |
@ -0,0 +1,161 @@
@@ -0,0 +1,161 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel\Support; |
||||
|
||||
/* |
||||
* helpers. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
|
||||
/** |
||||
* Generate a signature. |
||||
* |
||||
* @param array $attributes |
||||
* @param string $key |
||||
* @param string $encryptMethod |
||||
* |
||||
* @return string |
||||
*/ |
||||
function generate_sign(array $attributes, $key, $encryptMethod = 'md5') |
||||
{ |
||||
$rList = array(); |
||||
foreach ($attributes as $k => $v) { |
||||
if ($k == "other_settle_params" || $k == "app_id" || $k == "sign" || $k == "thirdparty_id") |
||||
continue; |
||||
$value = trim(strval($v)); |
||||
$len = strlen($value); |
||||
if ($len > 1 && substr($value, 0, 1) == "\"" && substr($value, $len, $len - 1) == "\"") |
||||
$value = substr($value, 1, $len - 1); |
||||
$value = trim($value); |
||||
if ($value == "" || $value == "null") |
||||
continue; |
||||
array_push($rList, $value); |
||||
} |
||||
array_push($rList, $key); |
||||
sort($rList, 2); |
||||
return call_user_func_array($encryptMethod, [implode('&', $rList)]); |
||||
} |
||||
|
||||
/** |
||||
* @param $message |
||||
* @param $token |
||||
* @return string |
||||
*/ |
||||
function generate_verify_sign($message, $token) |
||||
{ |
||||
$data = [ |
||||
$message['timestamp'], |
||||
(string)$message['nonce'], |
||||
(string)$message['msg'], |
||||
(string)$token |
||||
]; |
||||
sort($data, SORT_STRING); |
||||
return sha1(join('', $data)); |
||||
} |
||||
|
||||
/** |
||||
* @param string $signType |
||||
* @param string $secretKey |
||||
* |
||||
* @return \Closure|string |
||||
*/ |
||||
function get_encrypt_method(string $signType, string $secretKey = '') |
||||
{ |
||||
if ('HMAC-SHA256' === $signType) { |
||||
return function ($str) use ($secretKey) { |
||||
return hash_hmac('sha256', $str, $secretKey); |
||||
}; |
||||
} |
||||
|
||||
return 'md5'; |
||||
} |
||||
|
||||
/** |
||||
* Get client ip. |
||||
* |
||||
* @return string |
||||
*/ |
||||
function get_client_ip() |
||||
{ |
||||
if (!empty($_SERVER['REMOTE_ADDR'])) { |
||||
$ip = $_SERVER['REMOTE_ADDR']; |
||||
} else { |
||||
// for php-cli(phpunit etc.) |
||||
$ip = defined('PHPUNIT_RUNNING') ? '127.0.0.1' : gethostbyname(gethostname()); |
||||
} |
||||
|
||||
return filter_var($ip, FILTER_VALIDATE_IP) ?: '127.0.0.1'; |
||||
} |
||||
|
||||
/** |
||||
* Get current server ip. |
||||
* |
||||
* @return string |
||||
*/ |
||||
function get_server_ip() |
||||
{ |
||||
if (!empty($_SERVER['SERVER_ADDR'])) { |
||||
$ip = $_SERVER['SERVER_ADDR']; |
||||
} elseif (!empty($_SERVER['SERVER_NAME'])) { |
||||
$ip = gethostbyname($_SERVER['SERVER_NAME']); |
||||
} else { |
||||
// for php-cli(phpunit etc.) |
||||
$ip = defined('PHPUNIT_RUNNING') ? '127.0.0.1' : gethostbyname(gethostname()); |
||||
} |
||||
|
||||
return filter_var($ip, FILTER_VALIDATE_IP) ?: '127.0.0.1'; |
||||
} |
||||
|
||||
/** |
||||
* Return current url. |
||||
* |
||||
* @return string |
||||
*/ |
||||
function current_url() |
||||
{ |
||||
$protocol = 'http://'; |
||||
|
||||
if ((!empty($_SERVER['HTTPS']) && 'off' !== $_SERVER['HTTPS']) || ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? 'http') === 'https') { |
||||
$protocol = 'https://'; |
||||
} |
||||
|
||||
return $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; |
||||
} |
||||
|
||||
/** |
||||
* Return random string. |
||||
* |
||||
* @param string $length |
||||
* |
||||
* @return string |
||||
* @throws \ByteDance\Kernel\Exceptions\RuntimeException |
||||
*/ |
||||
|
||||
function str_random($length) |
||||
{ |
||||
return Str::random($length); |
||||
} |
||||
|
||||
/** |
||||
* @param string $content |
||||
* @param string $publicKey |
||||
* |
||||
* @return string |
||||
*/ |
||||
function rsa_public_encrypt($content, $publicKey) |
||||
{ |
||||
$encrypted = ''; |
||||
openssl_public_encrypt($content, $encrypted, openssl_pkey_get_public($publicKey), OPENSSL_PKCS1_OAEP_PADDING); |
||||
|
||||
return base64_encode($encrypted); |
||||
} |
@ -0,0 +1,194 @@
@@ -0,0 +1,194 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel\Support; |
||||
|
||||
use ByteDance\Kernel\Exceptions\RuntimeException; |
||||
|
||||
/** |
||||
* Class Str. |
||||
*/ |
||||
class Str |
||||
{ |
||||
/** |
||||
* The cache of snake-cased words. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected static $snakeCache = []; |
||||
|
||||
/** |
||||
* The cache of camel-cased words. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected static $camelCache = []; |
||||
|
||||
/** |
||||
* The cache of studly-cased words. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected static $studlyCache = []; |
||||
|
||||
/** |
||||
* Convert a value to camel case. |
||||
* |
||||
* @param string $value |
||||
* |
||||
* @return string |
||||
*/ |
||||
public static function camel($value) |
||||
{ |
||||
if (isset(static::$camelCache[$value])) { |
||||
return static::$camelCache[$value]; |
||||
} |
||||
|
||||
return static::$camelCache[$value] = lcfirst(static::studly($value)); |
||||
} |
||||
|
||||
/** |
||||
* Generate a more truly "random" alpha-numeric string. |
||||
* |
||||
* @param int $length |
||||
* |
||||
* @return string |
||||
* |
||||
* @throws \ByteDance\Kernel\Exceptions\RuntimeException |
||||
*/ |
||||
|
||||
public static function random($length = 16) |
||||
{ |
||||
$string = ''; |
||||
|
||||
while (($len = strlen($string)) < $length) { |
||||
$size = $length - $len; |
||||
|
||||
$bytes = static::randomBytes($size); |
||||
|
||||
$string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size); |
||||
} |
||||
|
||||
return $string; |
||||
} |
||||
|
||||
/** |
||||
* Generate a more truly "random" bytes. |
||||
* |
||||
* @param int $length |
||||
* |
||||
* @throws RuntimeException |
||||
* @throws \Exception |
||||
* |
||||
* @return string |
||||
* |
||||
* |
||||
* @codeCoverageIgnore |
||||
*/ |
||||
public static function randomBytes($length = 16) |
||||
{ |
||||
if (function_exists('random_bytes')) { |
||||
$bytes = random_bytes($length); |
||||
} elseif (function_exists('openssl_random_pseudo_bytes')) { |
||||
$bytes = openssl_random_pseudo_bytes($length, $strong); |
||||
if (false === $bytes || false === $strong) { |
||||
throw new RuntimeException('Unable to generate random string.'); |
||||
} |
||||
} else { |
||||
throw new RuntimeException('OpenSSL extension is required for PHP 5 users.'); |
||||
} |
||||
|
||||
return $bytes; |
||||
} |
||||
|
||||
/** |
||||
* Generate a "random" alpha-numeric string. |
||||
* |
||||
* Should not be considered sufficient for cryptography, etc. |
||||
* |
||||
* @param int $length |
||||
* |
||||
* @return string |
||||
*/ |
||||
public static function quickRandom($length = 16) |
||||
{ |
||||
$pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; |
||||
|
||||
return substr(str_shuffle(str_repeat($pool, $length)), 0, $length); |
||||
} |
||||
|
||||
/** |
||||
* Convert the given string to upper-case. |
||||
* |
||||
* @param string $value |
||||
* |
||||
* @return string |
||||
*/ |
||||
public static function upper($value) |
||||
{ |
||||
return mb_strtoupper($value); |
||||
} |
||||
|
||||
/** |
||||
* Convert the given string to title case. |
||||
* |
||||
* @param string $value |
||||
* |
||||
* @return string |
||||
*/ |
||||
public static function title($value) |
||||
{ |
||||
return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8'); |
||||
} |
||||
|
||||
/** |
||||
* Convert a string to snake case. |
||||
* |
||||
* @param string $value |
||||
* @param string $delimiter |
||||
* |
||||
* @return string |
||||
*/ |
||||
public static function snake($value, $delimiter = '_') |
||||
{ |
||||
$key = $value.$delimiter; |
||||
|
||||
if (isset(static::$snakeCache[$key])) { |
||||
return static::$snakeCache[$key]; |
||||
} |
||||
|
||||
if (!ctype_lower($value)) { |
||||
$value = strtolower(preg_replace('/(.)(?=[A-Z])/', '$1'.$delimiter, $value)); |
||||
} |
||||
|
||||
return static::$snakeCache[$key] = trim($value, '_'); |
||||
} |
||||
|
||||
/** |
||||
* Convert a value to studly caps case. |
||||
* |
||||
* @param string $value |
||||
* |
||||
* @return string |
||||
*/ |
||||
public static function studly($value) |
||||
{ |
||||
$key = $value; |
||||
|
||||
if (isset(static::$studlyCache[$key])) { |
||||
return static::$studlyCache[$key]; |
||||
} |
||||
|
||||
$value = ucwords(str_replace(['-', '_'], ' ', $value)); |
||||
|
||||
return static::$studlyCache[$key] = str_replace(' ', '', $value); |
||||
} |
||||
} |
@ -0,0 +1,247 @@
@@ -0,0 +1,247 @@
|
||||
<?php |
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel\Traits; |
||||
|
||||
use ByteDance\Kernel\Exceptions\InvalidArgumentException; |
||||
use ByteDance\Kernel\Support\Arr; |
||||
use ByteDance\Kernel\Support\Str; |
||||
|
||||
/** |
||||
* Trait Attributes. |
||||
*/ |
||||
trait HasAttributes |
||||
{ |
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $attributes = []; |
||||
/** |
||||
* @var bool |
||||
*/ |
||||
protected $snakeable = true; |
||||
|
||||
/** |
||||
* Set Attributes. |
||||
* |
||||
* @param array $attributes |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function setAttributes(array $attributes = []) |
||||
{ |
||||
$this->attributes = $attributes; |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Set attribute. |
||||
* |
||||
* @param string $attribute |
||||
* @param string $value |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function setAttribute($attribute, $value) |
||||
{ |
||||
Arr::set($this->attributes, $attribute, $value); |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Get attribute. |
||||
* |
||||
* @param string $attribute |
||||
* @param mixed $default |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public function getAttribute($attribute, $default = null) |
||||
{ |
||||
return Arr::get($this->attributes, $attribute, $default); |
||||
} |
||||
|
||||
/** |
||||
* @param string $attribute |
||||
* |
||||
* @return bool |
||||
*/ |
||||
public function isRequired($attribute) |
||||
{ |
||||
return in_array($attribute, $this->getRequired(), true); |
||||
} |
||||
|
||||
/** |
||||
* @return array|mixed |
||||
*/ |
||||
public function getRequired() |
||||
{ |
||||
return property_exists($this, 'required') ? $this->required : []; |
||||
} |
||||
|
||||
/** |
||||
* Set attribute. |
||||
* |
||||
* @param string $attribute |
||||
* @param mixed $value |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function with($attribute, $value) |
||||
{ |
||||
$this->snakeable && $attribute = Str::snake($attribute); |
||||
$this->setAttribute($attribute, $value); |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Override parent set() method. |
||||
* |
||||
* @param string $attribute |
||||
* @param mixed $value |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function set($attribute, $value) |
||||
{ |
||||
$this->setAttribute($attribute, $value); |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Override parent get() method. |
||||
* |
||||
* @param string $attribute |
||||
* @param mixed $default |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public function get($attribute, $default = null) |
||||
{ |
||||
return $this->getAttribute($attribute, $default); |
||||
} |
||||
|
||||
/** |
||||
* @param string $key |
||||
* |
||||
* @return bool |
||||
*/ |
||||
public function has(string $key) |
||||
{ |
||||
return Arr::has($this->attributes, $key); |
||||
} |
||||
|
||||
/** |
||||
* @param array $attributes |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function merge(array $attributes) |
||||
{ |
||||
$this->attributes = array_merge($this->attributes, $attributes); |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* @param array|string $keys |
||||
* |
||||
* @return array |
||||
*/ |
||||
public function only($keys) |
||||
{ |
||||
return Arr::only($this->attributes, $keys); |
||||
} |
||||
|
||||
/** |
||||
* Return all items. |
||||
* |
||||
* @return array |
||||
* @throws InvalidArgumentException |
||||
*/ |
||||
public function all() |
||||
{ |
||||
$this->checkRequiredAttributes(); |
||||
|
||||
return $this->attributes; |
||||
} |
||||
|
||||
/** |
||||
* Magic call. |
||||
* |
||||
* @param string $method |
||||
* @param array $args |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function __call($method, $args) |
||||
{ |
||||
if (0 === stripos($method, 'with')) { |
||||
return $this->with(substr($method, 4), array_shift($args)); |
||||
} |
||||
|
||||
throw new \BadMethodCallException(sprintf('Method "%s" does not exists.', $method)); |
||||
} |
||||
|
||||
/** |
||||
* Magic get. |
||||
* |
||||
* @param string $property |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public function __get($property) |
||||
{ |
||||
return $this->get($property); |
||||
} |
||||
|
||||
/** |
||||
* Magic set. |
||||
* |
||||
* @param string $property |
||||
* @param mixed $value |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function __set($property, $value) |
||||
{ |
||||
return $this->with($property, $value); |
||||
} |
||||
|
||||
/** |
||||
* Whether or not an data exists by key. |
||||
* |
||||
* @param string $key |
||||
* |
||||
* @return bool |
||||
*/ |
||||
public function __isset($key) |
||||
{ |
||||
return isset($this->attributes[$key]); |
||||
} |
||||
|
||||
/** |
||||
* Check required attributes. |
||||
* |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidArgumentException |
||||
*/ |
||||
protected function checkRequiredAttributes() |
||||
{ |
||||
foreach ($this->getRequired() as $attribute) { |
||||
if (is_null($this->get($attribute))) { |
||||
throw new InvalidArgumentException(sprintf('"%s" cannot be empty.', $attribute)); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,217 @@
@@ -0,0 +1,217 @@
|
||||
<?php |
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel\Traits; |
||||
|
||||
use GuzzleHttp\Client; |
||||
use GuzzleHttp\ClientInterface; |
||||
use GuzzleHttp\HandlerStack; |
||||
use Psr\Http\Message\ResponseInterface; |
||||
|
||||
/** |
||||
* Trait HasHttpRequests. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
trait HasHttpRequests |
||||
{ |
||||
use ResponseCastable; |
||||
/** |
||||
* @var \GuzzleHttp\ClientInterface |
||||
*/ |
||||
protected $httpClient; |
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $middlewares = []; |
||||
/** |
||||
* @var \GuzzleHttp\HandlerStack |
||||
*/ |
||||
protected $handlerStack; |
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected static $defaults = [ |
||||
'curl' => [ |
||||
CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4, |
||||
], |
||||
]; |
||||
|
||||
/** |
||||
* Set guzzle default settings. |
||||
* |
||||
* @param array $defaults |
||||
*/ |
||||
public static function setDefaultOptions($defaults = []) |
||||
{ |
||||
self::$defaults = $defaults; |
||||
} |
||||
|
||||
/** |
||||
* Return current guzzle default settings. |
||||
* |
||||
* @return array |
||||
*/ |
||||
public static function getDefaultOptions(): array |
||||
{ |
||||
return self::$defaults; |
||||
} |
||||
|
||||
/** |
||||
* Set GuzzleHttp\Client. |
||||
* |
||||
* @param \GuzzleHttp\ClientInterface $httpClient |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function setHttpClient(ClientInterface $httpClient) |
||||
{ |
||||
$this->httpClient = $httpClient; |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Return GuzzleHttp\ClientInterface instance. |
||||
* |
||||
* @return ClientInterface |
||||
*/ |
||||
public function getHttpClient(): ClientInterface |
||||
{ |
||||
if (!($this->httpClient instanceof ClientInterface)) { |
||||
if (property_exists($this, 'app') && $this->app['http_client']) { |
||||
$this->httpClient = $this->app['http_client']; |
||||
} else { |
||||
$this->httpClient = new Client(['handler' => HandlerStack::create($this->getGuzzleHandler())]); |
||||
} |
||||
} |
||||
|
||||
return $this->httpClient; |
||||
} |
||||
|
||||
/** |
||||
* Add a middleware. |
||||
* |
||||
* @param callable $middleware |
||||
* @param string|null $name |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function pushMiddleware(callable $middleware, string $name = null) |
||||
{ |
||||
if (!is_null($name)) { |
||||
$this->middlewares[$name] = $middleware; |
||||
} else { |
||||
array_push($this->middlewares, $middleware); |
||||
} |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Return all middlewares. |
||||
* |
||||
* @return array |
||||
*/ |
||||
public function getMiddlewares(): array |
||||
{ |
||||
return $this->middlewares; |
||||
} |
||||
|
||||
/** |
||||
* Make a request. |
||||
* |
||||
* @param string $url |
||||
* @param string $method |
||||
* @param array $options |
||||
* |
||||
* @return \Psr\Http\Message\ResponseInterface|\ByteDance\Kernel\Support\Collection|array|object|string |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function request($url, $method = 'GET', $options = []): ResponseInterface |
||||
{ |
||||
$method = strtoupper($method); |
||||
$options = array_merge(self::$defaults, $options, ['handler' => $this->getHandlerStack()]); |
||||
$options = $this->fixJsonIssue($options); |
||||
if (property_exists($this, 'baseUri') && !is_null($this->baseUri)) { |
||||
$options['base_uri'] = $this->baseUri; |
||||
} |
||||
$response = $this->getHttpClient()->request($method, $url, $options); |
||||
$response->getBody()->rewind(); |
||||
|
||||
return $response; |
||||
} |
||||
|
||||
/** |
||||
* @param \GuzzleHttp\HandlerStack $handlerStack |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function setHandlerStack(HandlerStack $handlerStack) |
||||
{ |
||||
$this->handlerStack = $handlerStack; |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Build a handler stack. |
||||
* |
||||
* @return \GuzzleHttp\HandlerStack |
||||
*/ |
||||
public function getHandlerStack(): HandlerStack |
||||
{ |
||||
if ($this->handlerStack) { |
||||
return $this->handlerStack; |
||||
} |
||||
$this->handlerStack = HandlerStack::create($this->getGuzzleHandler()); |
||||
foreach ($this->middlewares as $name => $middleware) { |
||||
$this->handlerStack->push($middleware, $name); |
||||
} |
||||
|
||||
return $this->handlerStack; |
||||
} |
||||
|
||||
/** |
||||
* @param array $options |
||||
* |
||||
* @return array |
||||
*/ |
||||
protected function fixJsonIssue(array $options): array |
||||
{ |
||||
if (isset($options['json']) && is_array($options['json'])) { |
||||
$options['headers'] = array_merge($options['headers'] ?? [], ['Content-Type' => 'application/json']); |
||||
if (empty($options['json'])) { |
||||
$options['body'] = \GuzzleHttp\json_encode($options['json'], JSON_FORCE_OBJECT); |
||||
} else { |
||||
$options['body'] = \GuzzleHttp\json_encode($options['json'], JSON_UNESCAPED_UNICODE); |
||||
} |
||||
unset($options['json']); |
||||
} |
||||
|
||||
return $options; |
||||
} |
||||
|
||||
/** |
||||
* Get guzzle handler. |
||||
* |
||||
* @return callable |
||||
*/ |
||||
protected function getGuzzleHandler() |
||||
{ |
||||
if (property_exists($this, 'app') && isset($this->app['guzzle_handler']) && is_string($this->app['guzzle_handler'])) { |
||||
$handler = $this->app['guzzle_handler']; |
||||
|
||||
return new $handler(); |
||||
} |
||||
|
||||
return \GuzzleHttp\choose_handler(); |
||||
} |
||||
} |
@ -0,0 +1,83 @@
@@ -0,0 +1,83 @@
|
||||
<?php |
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel\Traits; |
||||
|
||||
use Doctrine\Common\Cache\Cache; |
||||
use Doctrine\Common\Cache\FilesystemCache; |
||||
use ByteDance\Kernel\ServiceContainer; |
||||
use Psr\SimpleCache\CacheInterface; |
||||
use Symfony\Component\Cache\Adapter\FilesystemAdapter; |
||||
use Symfony\Component\Cache\Psr16Cache; |
||||
|
||||
/** |
||||
* Trait InteractsWithCache. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
trait InteractsWithCache |
||||
{ |
||||
/** |
||||
* @var \Psr\SimpleCache\CacheInterface |
||||
*/ |
||||
protected $cache; |
||||
|
||||
/** |
||||
* Get cache instance. |
||||
* |
||||
* @return \Psr\SimpleCache\CacheInterface |
||||
*/ |
||||
public function getCache() |
||||
{ |
||||
if ($this->cache) { |
||||
return $this->cache; |
||||
} |
||||
if (property_exists($this, 'app') && $this->app instanceof ServiceContainer |
||||
&& isset($this->app['cache']) && $this->app['cache'] instanceof CacheInterface) { |
||||
return $this->cache = $this->app['cache']; |
||||
} |
||||
|
||||
return $this->cache = $this->createDefaultCache(); |
||||
} |
||||
|
||||
/** |
||||
* Set cache instance. |
||||
* |
||||
* @param \Psr\SimpleCache\CacheInterface $cache |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function setCache(CacheInterface $cache) |
||||
{ |
||||
$this->cache = $cache; |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* @return Psr16Cache|\Symfony\Component\Cache\Simple\FilesystemCache |
||||
*/ |
||||
protected function createDefaultCache() |
||||
{ |
||||
if ($this->isSymfony43()) { |
||||
return new Psr16Cache(new FilesystemAdapter('bytedance', 1500)); |
||||
} |
||||
|
||||
return new \Symfony\Component\Cache\Simple\FilesystemCache(); |
||||
} |
||||
|
||||
/** |
||||
* @return bool |
||||
*/ |
||||
protected function isSymfony43(): bool |
||||
{ |
||||
return \class_exists('Symfony\Component\Cache\Psr16Cache'); |
||||
} |
||||
} |
@ -0,0 +1,241 @@
@@ -0,0 +1,241 @@
|
||||
<?php |
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel\Traits; |
||||
|
||||
use ByteDance\Kernel\Clauses\Clause; |
||||
use ByteDance\Kernel\Contracts\EventHandlerInterface; |
||||
use ByteDance\Kernel\Decorators\FinallyResult; |
||||
use ByteDance\Kernel\Decorators\TerminateResult; |
||||
use ByteDance\Kernel\Exceptions\InvalidArgumentException; |
||||
use ByteDance\Kernel\ServiceContainer; |
||||
|
||||
/** |
||||
* Trait Observable. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
trait Observable |
||||
{ |
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $handlers = []; |
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $clauses = []; |
||||
|
||||
/** |
||||
* @param \Closure|EventHandlerInterface|string $handler |
||||
* @param \Closure|EventHandlerInterface|string $condition |
||||
* |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \ReflectionException |
||||
* |
||||
* @return \ByteDance\Kernel\Clauses\Clause |
||||
*/ |
||||
public function push($handler, $condition = '*') |
||||
{ |
||||
list($handler, $condition) = $this->resolveHandlerAndCondition($handler, $condition); |
||||
if (!isset($this->handlers[$condition])) { |
||||
$this->handlers[$condition] = []; |
||||
} |
||||
array_push($this->handlers[$condition], $handler); |
||||
|
||||
return $this->newClause($handler); |
||||
} |
||||
|
||||
/** |
||||
* @param \Closure|EventHandlerInterface|string $handler |
||||
* @param \Closure|EventHandlerInterface|string $condition |
||||
* |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \ReflectionException |
||||
* |
||||
* @return \ByteDance\Kernel\Clauses\Clause |
||||
*/ |
||||
public function unshift($handler, $condition = '*') |
||||
{ |
||||
list($handler, $condition) = $this->resolveHandlerAndCondition($handler, $condition); |
||||
if (!isset($this->handlers[$condition])) { |
||||
$this->handlers[$condition] = []; |
||||
} |
||||
array_unshift($this->handlers[$condition], $handler); |
||||
|
||||
return $this->newClause($handler); |
||||
} |
||||
|
||||
/** |
||||
* @param string $condition |
||||
* @param \Closure|EventHandlerInterface|string $handler |
||||
* |
||||
* @return \ByteDance\Kernel\Clauses\Clause |
||||
* @throws \ReflectionException |
||||
* |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidArgumentException |
||||
*/ |
||||
public function observe($condition, $handler) |
||||
{ |
||||
return $this->push($handler, $condition); |
||||
} |
||||
|
||||
/** |
||||
* @param string $condition |
||||
* @param \Closure|EventHandlerInterface|string $handler |
||||
* |
||||
* @return \ByteDance\Kernel\Clauses\Clause |
||||
* @throws \ReflectionException |
||||
* |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidArgumentException |
||||
*/ |
||||
public function on($condition, $handler) |
||||
{ |
||||
return $this->push($handler, $condition); |
||||
} |
||||
|
||||
/** |
||||
* @param string|int $event |
||||
* @param mixed ...$payload |
||||
* |
||||
* @return mixed|null |
||||
*/ |
||||
public function dispatch($event, $payload) |
||||
{ |
||||
return $this->notify($event, $payload); |
||||
} |
||||
|
||||
/** |
||||
* @param string|int $event |
||||
* @param mixed ...$payload |
||||
* |
||||
* @return mixed|null |
||||
*/ |
||||
public function notify($event, $payload) |
||||
{ |
||||
$result = null; |
||||
foreach ($this->handlers as $condition => $handlers) { |
||||
if ('*' === $condition || ($condition & $event) === $event) { |
||||
foreach ($handlers as $handler) { |
||||
if ($clause = $this->clauses[spl_object_hash((object) $handler)] ?? null) { |
||||
if ($clause->intercepted($payload)) { |
||||
continue; |
||||
} |
||||
} |
||||
$response = $this->callHandler($handler, $payload); |
||||
switch (true) { |
||||
case $response instanceof TerminateResult: |
||||
return $response->content; |
||||
case true === $response: |
||||
continue 2; |
||||
case false === $response: |
||||
break 2; |
||||
case !empty($response) && !($result instanceof FinallyResult): |
||||
$result = $response; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
return $result instanceof FinallyResult ? $result->content : $result; |
||||
} |
||||
|
||||
/** |
||||
* @return array |
||||
*/ |
||||
public function getHandlers() |
||||
{ |
||||
return $this->handlers; |
||||
} |
||||
|
||||
/** |
||||
* @param mixed $handler |
||||
* |
||||
* @return \ByteDance\Kernel\Clauses\Clause |
||||
*/ |
||||
protected function newClause($handler): Clause |
||||
{ |
||||
return $this->clauses[spl_object_hash((object) $handler)] = new Clause(); |
||||
} |
||||
|
||||
/** |
||||
* @param callable $handler |
||||
* @param mixed $payload |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
protected function callHandler(callable $handler, $payload) |
||||
{ |
||||
try { |
||||
return $handler($payload); |
||||
} catch (\Exception $e) { |
||||
if (property_exists($this, 'app') && $this->app instanceof ServiceContainer) { |
||||
$this->app['logger']->error($e->getCode().': '.$e->getMessage(), [ |
||||
'code' => $e->getCode(), |
||||
'message' => $e->getMessage(), |
||||
'file' => $e->getFile(), |
||||
'line' => $e->getLine(), |
||||
]); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @param $handler |
||||
* |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \ReflectionException |
||||
* |
||||
* @return \Closure |
||||
*/ |
||||
protected function makeClosure($handler) |
||||
{ |
||||
if (is_callable($handler)) { |
||||
return $handler; |
||||
} |
||||
if (is_string($handler)) { |
||||
if (!class_exists($handler)) { |
||||
throw new InvalidArgumentException(sprintf('Class "%s" not exists.', $handler)); |
||||
} |
||||
if (!in_array(EventHandlerInterface::class, (new \ReflectionClass($handler))->getInterfaceNames(), true)) { |
||||
throw new InvalidArgumentException(sprintf('Class "%s" not an instance of "%s".', $handler, EventHandlerInterface::class)); |
||||
} |
||||
|
||||
return function ($payload) use ($handler) { |
||||
return (new $handler($this->app ?? null))->handle($payload); |
||||
}; |
||||
} |
||||
if ($handler instanceof EventHandlerInterface) { |
||||
return function () use ($handler) { |
||||
return $handler->handle(...func_get_args()); |
||||
}; |
||||
} |
||||
|
||||
throw new InvalidArgumentException('No valid handler is found in arguments.'); |
||||
} |
||||
|
||||
/** |
||||
* @param $handler |
||||
* @param $condition |
||||
* |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \ReflectionException |
||||
* |
||||
* @return array |
||||
*/ |
||||
protected function resolveHandlerAndCondition($handler, $condition): array |
||||
{ |
||||
if (is_int($handler) || (is_string($handler) && !class_exists($handler))) { |
||||
list($handler, $condition) = [$condition, $handler]; |
||||
} |
||||
|
||||
return [$this->makeClosure($handler), $condition]; |
||||
} |
||||
} |
@ -0,0 +1,89 @@
@@ -0,0 +1,89 @@
|
||||
<?php |
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Kernel\Traits; |
||||
|
||||
use ByteDance\Kernel\Contracts\Arrayable; |
||||
use ByteDance\Kernel\Exceptions\InvalidArgumentException; |
||||
use ByteDance\Kernel\Http\Response; |
||||
use ByteDance\Kernel\Support\Collection; |
||||
use Psr\Http\Message\ResponseInterface; |
||||
|
||||
/** |
||||
* Trait ResponseCastable. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
trait ResponseCastable |
||||
{ |
||||
/** |
||||
* @param \Psr\Http\Message\ResponseInterface $response |
||||
* @param string|null $type |
||||
* |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidArgumentException |
||||
* |
||||
* @return array|\ByteDance\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string |
||||
*/ |
||||
protected function castResponseToType(ResponseInterface $response, $type = null) |
||||
{ |
||||
$response = Response::buildFromPsrResponse($response); |
||||
$response->getBody()->rewind(); |
||||
switch ($type ?? 'array') { |
||||
case 'collection': |
||||
return $response->toCollection(); |
||||
case 'array': |
||||
return $response->toArray(); |
||||
case 'object': |
||||
return $response->toObject(); |
||||
case 'raw': |
||||
return $response; |
||||
default: |
||||
if (!is_subclass_of($type, Arrayable::class)) { |
||||
throw new InvalidArgumentException(sprintf( |
||||
'Config key "response_type" classname must be an instanceof %s', |
||||
Arrayable::class |
||||
)); |
||||
} |
||||
|
||||
return new $type($response); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @param mixed $response |
||||
* @param string|null $type |
||||
* |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidConfigException |
||||
* |
||||
* @return array|\ByteDance\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string |
||||
*/ |
||||
protected function detectAndCastResponseToType($response, $type = null) |
||||
{ |
||||
switch (true) { |
||||
case $response instanceof ResponseInterface: |
||||
$response = Response::buildFromPsrResponse($response); |
||||
break; |
||||
case $response instanceof Arrayable: |
||||
$response = new Response(200, [], json_encode($response->toArray())); |
||||
break; |
||||
case ($response instanceof Collection) || is_array($response) || is_object($response): |
||||
$response = new Response(200, [], json_encode($response)); |
||||
break; |
||||
case is_scalar($response): |
||||
$response = new Response(200, [], $response); |
||||
break; |
||||
default: |
||||
throw new InvalidArgumentException(sprintf('Unsupported response type "%s"', gettype($response))); |
||||
} |
||||
|
||||
return $this->castResponseToType($response, $type); |
||||
} |
||||
} |
@ -0,0 +1,38 @@
@@ -0,0 +1,38 @@
|
||||
<?php |
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\MiniProgram; |
||||
|
||||
use ByteDance\Kernel\ServiceContainer; |
||||
|
||||
/** |
||||
* Class Application. |
||||
* |
||||
* @property \ByteDance\MiniProgram\Auth\AccessToken $access_token |
||||
* @property \ByteDance\MiniProgram\Auth\Client $auth |
||||
* @property \ByteDance\MiniProgram\KVData\Client $kv |
||||
* @property \ByteDance\MiniProgram\Encryptor $encryptor |
||||
* @property \ByteDance\MiniProgram\QRCode\Client $qrcode |
||||
* @property \ByteDance\MiniProgram\Message\Client $message |
||||
* @property \ByteDance\MiniProgram\Payment\Client $payment |
||||
*/ |
||||
class Application extends ServiceContainer |
||||
{ |
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $providers = [ |
||||
Auth\ServiceProvider::class, |
||||
KVData\ServiceProvider::class, |
||||
QRCode\ServiceProvider::class, |
||||
Message\ServiceProvider::class, |
||||
Payment\ServiceProvider::class, |
||||
]; |
||||
} |
@ -0,0 +1,43 @@
@@ -0,0 +1,43 @@
|
||||
<?php |
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\MiniProgram\Auth; |
||||
|
||||
use ByteDance\Kernel\AccessToken as BaseAccessToken; |
||||
|
||||
/** |
||||
* Class AuthorizerAccessToken. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class AccessToken extends BaseAccessToken |
||||
{ |
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $endpointToGetToken = 'https://developer.toutiao.com/api/apps/token'; |
||||
|
||||
/** |
||||
* @return array |
||||
*/ |
||||
protected function getCredentials(): array |
||||
{ |
||||
$client_secret = $this->app['config']['app_secret']; |
||||
date_default_timezone_set('Asia/Shanghai'); |
||||
$client_id = $this->app['config']['app_id']; |
||||
$grand_type = 'client_credential'; |
||||
|
||||
return [ |
||||
'grant_type' => $grand_type, |
||||
'appid' => $client_id, |
||||
'secret' => $client_secret, |
||||
]; |
||||
} |
||||
} |
@ -0,0 +1,46 @@
@@ -0,0 +1,46 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\MiniProgram\Auth; |
||||
|
||||
use ByteDance\Kernel\BaseClient; |
||||
|
||||
/** |
||||
* Class Auth. |
||||
* |
||||
* @author mingyoung <mingyoungcheung@gmail.com> |
||||
*/ |
||||
class Client extends BaseClient |
||||
{ |
||||
/** |
||||
* Get session info by code. |
||||
* @param string $code |
||||
* @param bool $anonymous |
||||
* @return array|\ByteDance\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function session(string $code, bool $anonymous = false) |
||||
{ |
||||
$params = [ |
||||
'appid' => $this->app['config']['app_id'], |
||||
'secret' => $this->app['config']['app_secret'], |
||||
]; |
||||
if ($anonymous) { |
||||
$params['anonymous_code'] = $code; |
||||
} else { |
||||
$params['code'] = $code; |
||||
} |
||||
|
||||
return $this->httpGet('api/apps/jscode2session', $params); |
||||
} |
||||
} |
@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
|
||||
<?php |
||||
/* |
||||
* This file is part of the otkurbiz/bytedance. |
||||
* |
||||
* (c) |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\MiniProgram\Auth; |
||||
|
||||
use ByteDance\MiniProgram\Encryptor; |
||||
use Pimple\Container; |
||||
use Pimple\ServiceProviderInterface; |
||||
|
||||
/** |
||||
* Class ServiceProvider. |
||||
* |
||||
* @author |
||||
*/ |
||||
class ServiceProvider implements ServiceProviderInterface |
||||
{ |
||||
/** |
||||
* {@inheritdoc}. |
||||
*/ |
||||
public function register(Container $app) |
||||
{ |
||||
!isset($app['access_token']) && $app['access_token'] = function ($app) { |
||||
return new AccessToken($app); |
||||
}; |
||||
!isset($app['auth']) && $app['auth'] = function ($app) { |
||||
return new Client($app); |
||||
}; |
||||
|
||||
!isset($app['encryptor']) && $app['encryptor'] = function ($app) { |
||||
return new Encryptor(); |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,48 @@
@@ -0,0 +1,48 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\MiniProgram; |
||||
|
||||
use ByteDance\Kernel\Encryptor as BaseEncryptor; |
||||
use ByteDance\Kernel\Exceptions\DecryptException; |
||||
use ByteDance\Kernel\Support\AES; |
||||
|
||||
|
||||
/** |
||||
* Class Encryptor. |
||||
* |
||||
* @author mingyoung <mingyoungcheung@gmail.com> |
||||
*/ |
||||
class Encryptor extends BaseEncryptor |
||||
{ |
||||
/** |
||||
* Decrypt data. |
||||
* @param string $sessionKey |
||||
* @param string $iv |
||||
* @param string $encrypted |
||||
* @return array |
||||
* @throws DecryptException |
||||
*/ |
||||
public function decryptData(string $sessionKey, string $iv, string $encrypted): array |
||||
{ |
||||
|
||||
$decrypted = AES::decrypt( |
||||
base64_decode($encrypted, false), base64_decode($sessionKey, false), base64_decode($iv, false) |
||||
); |
||||
|
||||
$decrypted = json_decode($this->pkcs7Unpad($decrypted), true); |
||||
if (!$decrypted) { |
||||
throw new DecryptException('The given payload is invalid.'); |
||||
} |
||||
|
||||
return $decrypted; |
||||
} |
||||
} |
@ -0,0 +1,68 @@
@@ -0,0 +1,68 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the OtkurBiz/ByteDance. |
||||
* |
||||
* (c) |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\MiniProgram\KVData; |
||||
|
||||
use ByteDance\Kernel\BaseClient; |
||||
|
||||
/** |
||||
* Class Client. |
||||
* |
||||
* @author |
||||
*/ |
||||
class Client extends BaseClient |
||||
{ |
||||
/** |
||||
* Set User Storage |
||||
* 当 key 是开发者所配置的排行榜 key 时,value 的内容应该满足KVData所指出的形式, 即形如 "{\"ttgame\":{\"score\":1}}". |
||||
* |
||||
* openid 登录用户唯一标识 |
||||
* signature 用户登录态签名,参考用户登录态签名算法 |
||||
* sig_method 用户登录态签名的编码方法,参考用户登录态签名算法 |
||||
* kv_list (body 中) 要设置的用户数据 |
||||
* |
||||
* @param string $openid |
||||
* @param string $signature |
||||
* @param string $sig_method |
||||
* @param string $kv_list |
||||
* @return array|\ByteDance\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function set(string $openid, string $signature, string $sig_method, string $kv_list) |
||||
{ |
||||
return $this->httpPost('api/apps/set_user_storage', ['openid'=>$openid, 'signature' => $signature, 'sig_method'=>$sig_method, 'kv_list'=>$kv_list]); |
||||
} |
||||
|
||||
/** |
||||
* Set User Storage |
||||
* 当 key 是开发者所配置的排行榜 key 时,value 的内容应该满足KVData所指出的形式, 即形如 "{\"ttgame\":{\"score\":1}}". |
||||
* |
||||
* openid 登录用户唯一标识 |
||||
* signature 用户登录态签名,参考用户登录态签名算法 |
||||
* sig_method 用户登录态签名的编码方法,参考用户登录态签名算法 |
||||
* key (body 中) 要删除的用户数据的 key list |
||||
* |
||||
* @param string $openid |
||||
* @param string $signature |
||||
* @param string $sig_method |
||||
* @param string $key |
||||
* @return array|\ByteDance\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function remove(string $openid, string $signature, string $sig_method, string $key) |
||||
{ |
||||
return $this->httpPost('api/apps/remove_user_storage', ['openid'=>$openid, 'signature' => $signature, 'sig_method'=>$sig_method, 'key'=>$key]); |
||||
} |
||||
} |
@ -0,0 +1,32 @@
@@ -0,0 +1,32 @@
|
||||
<?php |
||||
/* |
||||
* This file is part of the otkurbiz/bytedance. |
||||
* |
||||
* (c) |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\MiniProgram\KVData; |
||||
|
||||
use Pimple\Container; |
||||
use Pimple\ServiceProviderInterface; |
||||
|
||||
/** |
||||
* Class ServiceProvider. |
||||
* |
||||
* @author |
||||
*/ |
||||
class ServiceProvider implements ServiceProviderInterface |
||||
{ |
||||
/** |
||||
* {@inheritdoc}. |
||||
*/ |
||||
public function register(Container $app) |
||||
{ |
||||
!isset($app['kv']) && $app['kv'] = function ($app) { |
||||
return new Client($app); |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,55 @@
@@ -0,0 +1,55 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the OtkurBiz/ByteDance. |
||||
* |
||||
* (c) |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\MiniProgram\Message; |
||||
|
||||
use ByteDance\Kernel\BaseClient; |
||||
|
||||
/** |
||||
* Class Client. |
||||
* |
||||
* @author |
||||
*/ |
||||
class Client extends BaseClient |
||||
{ |
||||
/** |
||||
* |
||||
* 发送模版消息 |
||||
* 目前只有今日头条支持,抖音和 lite 接入中 |
||||
* |
||||
* touser String 是 要发送给用户的 open id, open id 的获取请参考登录 |
||||
* template_id String 是 在开发者平台配置消息模版后获得的模版 id |
||||
* page String 否 点击消息卡片之后打开的小程序页面地址,空则无跳转 |
||||
* form_id String 是 可以通过<form />组件获得 form_id, 获取方法 |
||||
* data dict<String, SubData> 是 模板中填充着的数据,key 必须是 keyword 为前缀 |
||||
*SubData |
||||
*SubData 也是 dict,结构如下: |
||||
*名称 类型 是否必填 |
||||
*value String 是 |
||||
* eg: {"access_token": "YOUR_ACCESS_TOKEN", "app_id": "YOUR_APP_ID", "data": {"keyword1": {"value": "v1"}, "keyword2": {"value": "v2"}}, "page": "pages/index", "form_id": "YOUR_FORM_ID", "touser": "USER_OPEN_ID", "template_id": "YOUR_TPL_ID"} |
||||
* |
||||
* @param string $touser |
||||
* @param string $template_id |
||||
* @param string $page |
||||
* @param string $form_id |
||||
* @param mixed $data |
||||
* @return array|\ByteDance\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function create(string $touser, string $template_id, string $page, string $form_id, mixed $data) |
||||
{ |
||||
return $this->httpPost('api/apps/game/template/send', ['touser'=>$touser, 'template_id' => $template_id, 'page'=>$page, 'form_id'=>$form_id, 'data'=>$data]); |
||||
} |
||||
|
||||
|
||||
} |
@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
|
||||
<?php |
||||
/* |
||||
* This file is part of the otkurbiz/bytedance. |
||||
* |
||||
* (c) |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
namespace ByteDance\MiniProgram\Message; |
||||
use Pimple\Container; |
||||
use Pimple\ServiceProviderInterface; |
||||
|
||||
/** |
||||
* Class ServiceProvider. |
||||
* |
||||
* @author |
||||
*/ |
||||
class ServiceProvider implements ServiceProviderInterface |
||||
{ |
||||
/** |
||||
* {@inheritdoc}. |
||||
*/ |
||||
public function register(Container $app) |
||||
{ |
||||
!isset($app['message']) && $app['message'] = function ($app) { |
||||
return new Client($app); |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,91 @@
@@ -0,0 +1,91 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the OtkurBiz/ByteDance. |
||||
* |
||||
* (c) |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\MiniProgram\Payment; |
||||
|
||||
use ByteDance\Kernel\BaseClient; |
||||
use ByteDance\Kernel\Support; |
||||
|
||||
/** |
||||
* Class Client. |
||||
* |
||||
* @author |
||||
*/ |
||||
class Client extends BaseClient |
||||
{ |
||||
/** |
||||
* @param string $uid |
||||
* @param string $outTradeNo |
||||
* @param int $totalAmount |
||||
* @param string $subject |
||||
* @param array $optional |
||||
* @return array |
||||
*/ |
||||
public function pay(string $uid, string $outTradeNo, int $totalAmount, string $subject, array $optional = []) |
||||
{ |
||||
$appId = $this->app['config']['pay_app_id']; |
||||
$mchId = $this->app['config']['mch_id']; |
||||
$secretKey = $this->app['config']['key']; |
||||
$params = [ |
||||
'app_id' => $appId, |
||||
'merchant_id' => $mchId, |
||||
'timestamp' => $time = (string)time(), |
||||
'sign_type' => 'MD5', |
||||
'out_order_no' => $outTradeNo, |
||||
'total_amount' => $totalAmount, |
||||
'product_code' => 'pay', |
||||
'payment_type' => 'direct', |
||||
'trade_type' => 'H5', |
||||
'version' => '2.0', |
||||
'currency' => 'CNY', |
||||
'subject' => $subject, |
||||
'body' => $optional['body'] ?? $subject, |
||||
'uid' => $uid, |
||||
'trade_time' => $time, |
||||
'valid_time' => $optional['valid_time'] ?? '300', |
||||
'notify_url' => $optional['notify_url'] ?? 'https://www.baidu.com' |
||||
]; |
||||
if (!empty($optional['wx_url'])) { |
||||
$params['wx_type'] = 'MWEB'; |
||||
$params['wx_url'] = $optional['wx_url']; |
||||
} |
||||
if (!empty($optional['alipay_url'])) { |
||||
$params['alipay_url'] = $optional['alipay_url']; |
||||
} |
||||
$params['sign'] = $this->sign($params, $secretKey); |
||||
$params['risk_info'] = $optional['risk_info'] ?? json_encode(['ip' => $ip = Support\get_client_ip()]); |
||||
return $params; |
||||
} |
||||
|
||||
/** |
||||
* @param array $data |
||||
* @param string $secretKey |
||||
* @return string |
||||
*/ |
||||
public function sign(array $data, string $secretKey) |
||||
{ |
||||
$signData = ''; |
||||
ksort($data); |
||||
foreach ($data as $k => $v) { |
||||
if (is_array($v)) { |
||||
$value = json_encode($v); |
||||
} else { |
||||
$value = $v; |
||||
} |
||||
if ($value) { |
||||
$signData .= '&' . $k . '=' . $value; |
||||
} |
||||
} |
||||
|
||||
$signData = ltrim($signData, '&'); |
||||
return md5($signData . $secretKey); |
||||
} |
||||
} |
@ -0,0 +1,32 @@
@@ -0,0 +1,32 @@
|
||||
<?php |
||||
/* |
||||
* This file is part of the otkurbiz/bytedance. |
||||
* |
||||
* (c) |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\MiniProgram\Payment; |
||||
|
||||
use Pimple\Container; |
||||
use Pimple\ServiceProviderInterface; |
||||
|
||||
/** |
||||
* Class ServiceProvider. |
||||
* |
||||
* @author |
||||
*/ |
||||
class ServiceProvider implements ServiceProviderInterface |
||||
{ |
||||
/** |
||||
* {@inheritdoc}. |
||||
*/ |
||||
public function register(Container $app) |
||||
{ |
||||
!isset($app['payment']) && $app['payment'] = function ($app) { |
||||
return new Client($app); |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,41 @@
@@ -0,0 +1,41 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the OtkurBiz/ByteDance. |
||||
* |
||||
* (c) |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\MiniProgram\QRCode; |
||||
|
||||
use ByteDance\Kernel\BaseClient; |
||||
|
||||
/** |
||||
* Class Client. |
||||
* |
||||
* @author |
||||
*/ |
||||
class Client extends BaseClient |
||||
{ |
||||
/** |
||||
* @param array $params |
||||
* @return mixed|string |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function create(array $params = []) |
||||
{ |
||||
$response= $this->httpPostJson('api/apps/qrcode',$params); |
||||
$responses = $response->getBody()->getContents(); |
||||
$res = @json_decode($responses,1); |
||||
if ($res){ |
||||
return $res; |
||||
}else{ |
||||
return $responses; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,32 @@
@@ -0,0 +1,32 @@
|
||||
<?php |
||||
/* |
||||
* This file is part of the otkurbiz/bytedance. |
||||
* |
||||
* (c) |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\MiniProgram\QRCode; |
||||
|
||||
use Pimple\Container; |
||||
use Pimple\ServiceProviderInterface; |
||||
|
||||
/** |
||||
* Class ServiceProvider. |
||||
* |
||||
* @author |
||||
*/ |
||||
class ServiceProvider implements ServiceProviderInterface |
||||
{ |
||||
/** |
||||
* {@inheritdoc}. |
||||
*/ |
||||
public function register(Container $app) |
||||
{ |
||||
!isset($app['qrcode']) && $app['qrcode'] = function ($app) { |
||||
return new Client($app); |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,69 @@
@@ -0,0 +1,69 @@
|
||||
<?php |
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Payment; |
||||
|
||||
use ByteDance\Kernel\ServiceContainer; |
||||
use Closure; |
||||
|
||||
/** |
||||
* Class Application. |
||||
* |
||||
* @property \ByteDance\Payment\Order\Client $order |
||||
* @property \ByteDance\Payment\Refund\Client $refund |
||||
*/ |
||||
class Application extends ServiceContainer |
||||
{ |
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $providers = [ |
||||
Order\ServiceProvider::class, |
||||
Refund\ServiceProvider::class |
||||
]; |
||||
|
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $defaultConfig = [ |
||||
'http' => [ |
||||
'base_uri' => 'https://developer.toutiao.com/api/apps/', |
||||
], |
||||
]; |
||||
/** |
||||
* Set sub-merchant. |
||||
* |
||||
* @param string $mchId |
||||
* @param string|null $appId |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function setSubMerchant(string $mchId) |
||||
{ |
||||
$this['config']->set('thirdparty_id', $mchId); |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
public function getKey() |
||||
{ |
||||
return $this['config']->key; |
||||
} |
||||
|
||||
/** |
||||
* 支付回调 |
||||
* @param Closure $closure |
||||
* @return \Symfony\Component\HttpFoundation\Response |
||||
*/ |
||||
public function handlePaidNotify(Closure $closure) |
||||
{ |
||||
return (new Notify\Paid($this))->handle($closure); |
||||
} |
||||
} |
@ -0,0 +1,189 @@
@@ -0,0 +1,189 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Payment\Kernel; |
||||
|
||||
use ByteDance\Kernel\Support; |
||||
use ByteDance\Kernel\Traits\HasHttpRequests; |
||||
use ByteDance\Payment\Application; |
||||
use GuzzleHttp\MessageFormatter; |
||||
use GuzzleHttp\Middleware; |
||||
use Psr\Http\Message\ResponseInterface; |
||||
|
||||
/** |
||||
* Class BaseClient. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class BaseClient |
||||
{ |
||||
use HasHttpRequests { |
||||
request as performRequest; |
||||
} |
||||
|
||||
/** |
||||
* @var \ByteDance\Payment\Application |
||||
*/ |
||||
protected $app; |
||||
|
||||
/** |
||||
* Constructor. |
||||
* |
||||
* @param \ByteDance\Payment\Application $app |
||||
*/ |
||||
public function __construct(Application $app) |
||||
{ |
||||
$this->app = $app; |
||||
|
||||
$this->setHttpClient($this->app['http_client']); |
||||
} |
||||
|
||||
/** |
||||
* Extra request params. |
||||
* |
||||
* @return array |
||||
*/ |
||||
protected function prepends() |
||||
{ |
||||
return []; |
||||
} |
||||
|
||||
/** |
||||
* Make a API request. |
||||
* |
||||
* @param string $endpoint |
||||
* @param array $params |
||||
* @param string $method |
||||
* @param array $options |
||||
* @param bool $returnResponse |
||||
* |
||||
* @return \Psr\Http\Message\ResponseInterface|\ByteDance\Kernel\Support\Collection|array|object|string |
||||
* |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
protected function request(string $endpoint, array $params = [], $method = 'post', array $options = [], $returnResponse = false) |
||||
{ |
||||
$base = [ |
||||
'app_id' => $this->app['config']['app_id'], |
||||
'thirdparty_id' => $this->app['config']['thirdparty_id'] |
||||
]; |
||||
|
||||
$params = array_filter(array_merge($base, $this->prepends(), $params), 'strlen'); |
||||
|
||||
$secretKey = $this->app->getKey(); |
||||
|
||||
// $encryptMethod = Support\get_encrypt_method(Support\Arr::get($params, 'sign_type', 'MD5'), $secretKey); |
||||
|
||||
$params['sign'] = Support\generate_sign($params, $secretKey); |
||||
|
||||
$options = array_merge([ |
||||
'json' => $params, |
||||
], $options); |
||||
$this->pushMiddleware($this->logMiddleware(), 'log'); |
||||
|
||||
$response = $this->performRequest($endpoint, $method, $options); |
||||
|
||||
return $returnResponse ? $response : $this->castResponseToType($response, $this->app->config->get('response_type')); |
||||
} |
||||
|
||||
/** |
||||
* Log the request. |
||||
* |
||||
* @return \Closure |
||||
*/ |
||||
protected function logMiddleware() |
||||
{ |
||||
$formatter = new MessageFormatter($this->app['config']['http.log_template'] ?? MessageFormatter::DEBUG); |
||||
|
||||
return Middleware::log($this->app['logger'], $formatter); |
||||
} |
||||
|
||||
/** |
||||
* Make a request and return raw response. |
||||
* |
||||
* @param string $endpoint |
||||
* @param array $params |
||||
* @param string $method |
||||
* @param array $options |
||||
* |
||||
* @return ResponseInterface |
||||
* |
||||
* @throws \HttpBase\Exceptions\InvalidConfigException |
||||
* @throws \HttpBase\Exceptions\InvalidArgumentException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
protected function requestRaw(string $endpoint, array $params = [], $method = 'post', array $options = []) |
||||
{ |
||||
/** @var ResponseInterface $response */ |
||||
$response = $this->request($endpoint, $params, $method, $options, true); |
||||
|
||||
return $response; |
||||
} |
||||
|
||||
/** |
||||
* Make a request and return an array. |
||||
* |
||||
* @param string $endpoint |
||||
* @param array $params |
||||
* @param string $method |
||||
* @param array $options |
||||
* |
||||
* @return array |
||||
* |
||||
* @throws \HttpBase\Exceptions\InvalidArgumentException |
||||
* @throws \HttpBase\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
protected function requestArray(string $endpoint, array $params = [], $method = 'post', array $options = []): array |
||||
{ |
||||
$response = $this->requestRaw($endpoint, $params, $method, $options); |
||||
|
||||
return $this->castResponseToType($response, 'array'); |
||||
} |
||||
|
||||
/** |
||||
* Request with SSL. |
||||
* |
||||
* @param string $endpoint |
||||
* @param array $params |
||||
* @param string $method |
||||
* @param array $options |
||||
* |
||||
* @return \Psr\Http\Message\ResponseInterface|\Wechat\Kernel\Support\Collection|array|object|string |
||||
* |
||||
* @throws \HttpBase\Exceptions\InvalidConfigException |
||||
* @throws \HttpBase\Exceptions\InvalidArgumentException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
protected function safeRequest($endpoint, array $params, $method = 'post', array $options = []) |
||||
{ |
||||
$options = array_merge([ |
||||
'cert' => $this->app['config']->get('cert_path'), |
||||
'ssl_key' => $this->app['config']->get('key_path'), |
||||
], $options); |
||||
|
||||
return $this->request($endpoint, $params, $method, $options); |
||||
} |
||||
|
||||
/** |
||||
* Wrapping an API endpoint. |
||||
* |
||||
* @param string $endpoint |
||||
* |
||||
* @return string |
||||
*/ |
||||
protected function wrap(string $endpoint): string |
||||
{ |
||||
return $this->app->inSandbox() ? "sandboxnew/{$endpoint}" : $endpoint; |
||||
} |
||||
} |
@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Payment\Kernel\Exceptions; |
||||
|
||||
use ByteDance\Kernel\Exceptions\Exception; |
||||
|
||||
class InvalidSignException extends Exception |
||||
{ |
||||
} |
@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Payment\Kernel\Exceptions; |
||||
|
||||
use ByteDance\Kernel\Exceptions\Exception; |
||||
|
||||
class SandboxException extends Exception |
||||
{ |
||||
} |
@ -0,0 +1,197 @@
@@ -0,0 +1,197 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Payment\Notify; |
||||
|
||||
use Closure; |
||||
use ByteDance\Kernel\Exceptions\Exception; |
||||
use ByteDance\Kernel\Support; |
||||
use ByteDance\Payment\Kernel\Exceptions\InvalidSignException; |
||||
use Symfony\Component\HttpFoundation\Response; |
||||
|
||||
abstract class Handler |
||||
{ |
||||
const SUCCESS = 'success'; |
||||
const FAIL = 'fail'; |
||||
|
||||
/** |
||||
* @var \ByteDance\Payment\Application |
||||
*/ |
||||
protected $app; |
||||
|
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $message; |
||||
|
||||
/** |
||||
* @var string|null |
||||
*/ |
||||
protected $fail; |
||||
|
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $attributes = []; |
||||
|
||||
/** |
||||
* Check sign. |
||||
* If failed, throws an exception. |
||||
* |
||||
* @var bool |
||||
*/ |
||||
protected $check = true; |
||||
|
||||
/** |
||||
* Respond with sign. |
||||
* |
||||
* @var bool |
||||
*/ |
||||
protected $sign = false; |
||||
|
||||
/** |
||||
* @param \ByteDance\Payment\Application $app |
||||
*/ |
||||
public function __construct($app) |
||||
{ |
||||
$this->app = $app; |
||||
} |
||||
|
||||
/** |
||||
* Handle incoming notify. |
||||
* |
||||
* @param \Closure $closure |
||||
* |
||||
* @return \Symfony\Component\HttpFoundation\Response |
||||
*/ |
||||
abstract public function handle(Closure $closure); |
||||
|
||||
/** |
||||
* @param string $message |
||||
*/ |
||||
public function fail(string $message) |
||||
{ |
||||
$this->fail = $message; |
||||
} |
||||
|
||||
/** |
||||
* @param array $attributes |
||||
* @param bool $sign |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function respondWith(array $attributes, bool $sign = false) |
||||
{ |
||||
$this->attributes = $attributes; |
||||
$this->sign = $sign; |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Build xml and return the response to WeChat. |
||||
* |
||||
* @return \Symfony\Component\HttpFoundation\Response |
||||
* |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidArgumentException |
||||
*/ |
||||
public function toResponse(): Response |
||||
{ |
||||
$base = [ |
||||
'err_no' => is_null($this->fail) ? 0 : 400, |
||||
'err_tips' => is_null($this->fail) ? static::SUCCESS : static::FAIL, |
||||
]; |
||||
return new Response(json_encode($base)); |
||||
} |
||||
|
||||
/** |
||||
* Return the notify message from request. |
||||
* |
||||
* @return array |
||||
* |
||||
* @throws \ByteDance\Kernel\Exceptions\Exception |
||||
*/ |
||||
public function getMessage(): array |
||||
{ |
||||
if (!empty($this->message)) { |
||||
return $this->message; |
||||
} |
||||
|
||||
try { |
||||
$message = json_decode(strval($this->app['request']->getContent()), true); |
||||
} catch (\Throwable $e) { |
||||
throw new Exception('Invalid request json: ' . $e->getMessage(), 400); |
||||
} |
||||
|
||||
if (!is_array($message) || empty($message)) { |
||||
throw new Exception('Invalid request json.', 400); |
||||
} |
||||
|
||||
if ($this->check) { |
||||
$this->validate($message); |
||||
} |
||||
|
||||
return $this->message = json_decode($message['msg'], true); |
||||
} |
||||
|
||||
/** |
||||
* Decrypt message. |
||||
* |
||||
* @param string $key |
||||
* |
||||
* @return string|null |
||||
* |
||||
* @throws \ByteDance\Kernel\Exceptions\Exception |
||||
*/ |
||||
public function decryptMessage(string $key) |
||||
{ |
||||
$message = $this->getMessage(); |
||||
if (empty($message[$key])) { |
||||
return null; |
||||
} |
||||
|
||||
return Support\AES::decrypt( |
||||
base64_decode($message[$key], true), |
||||
md5($this->app['config']->key), |
||||
'', |
||||
OPENSSL_RAW_DATA, |
||||
'AES-256-ECB' |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Validate the request params. |
||||
* |
||||
* @param array $message |
||||
* |
||||
* @throws \ByteDance\Payment\Kernel\Exceptions\InvalidSignException |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidArgumentException |
||||
*/ |
||||
protected function validate(array $message) |
||||
{ |
||||
$sign = $message['msg_signature']; |
||||
unset($message['msg_signature']); |
||||
|
||||
if (Support\generate_verify_sign($message, $this->app['config']->token) !== $sign) { |
||||
throw new InvalidSignException(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @param mixed $result |
||||
*/ |
||||
protected function strict($result) |
||||
{ |
||||
if (true !== $result && is_null($this->fail)) { |
||||
$this->fail(strval($result)); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,32 @@
@@ -0,0 +1,32 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Payment\Notify; |
||||
|
||||
use Closure; |
||||
|
||||
class Paid extends Handler |
||||
{ |
||||
/** |
||||
* @param \Closure $closure |
||||
* |
||||
* @return \Symfony\Component\HttpFoundation\Response |
||||
* |
||||
*/ |
||||
public function handle(Closure $closure) |
||||
{ |
||||
$this->strict( |
||||
\call_user_func($closure, $this->getMessage(), [$this, 'fail']) |
||||
); |
||||
|
||||
return $this->toResponse(); |
||||
} |
||||
} |
@ -0,0 +1,46 @@
@@ -0,0 +1,46 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Payment\Notify; |
||||
|
||||
use Closure; |
||||
|
||||
class Refunded extends Handler |
||||
{ |
||||
protected $check = false; |
||||
|
||||
/** |
||||
* @param \Closure $closure |
||||
* |
||||
* @return \Symfony\Component\HttpFoundation\Response |
||||
* |
||||
*/ |
||||
public function handle(Closure $closure) |
||||
{ |
||||
$this->strict( |
||||
\call_user_func($closure, $this->getMessage(), $this->reqInfo(), [$this, 'fail']) |
||||
); |
||||
|
||||
return $this->toResponse(); |
||||
} |
||||
|
||||
/** |
||||
* Decrypt the `req_info` from request message. |
||||
* |
||||
* @return array |
||||
* |
||||
* @throws \HttpBase\Exceptions\Exception |
||||
*/ |
||||
public function reqInfo() |
||||
{ |
||||
|
||||
} |
||||
} |
@ -0,0 +1,49 @@
@@ -0,0 +1,49 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the OtkurBiz/ByteDance. |
||||
* |
||||
* (c) |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Payment\Order; |
||||
|
||||
use ByteDance\Payment\Kernel\BaseClient; |
||||
|
||||
/** |
||||
* Class Client. |
||||
* |
||||
* @author |
||||
*/ |
||||
class Client extends BaseClient |
||||
{ |
||||
/** |
||||
* 下单 |
||||
* @param $params |
||||
* @return array|\ByteDance\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function unify($params) |
||||
{ |
||||
return $this->request('ecpay/v1/create_order', $params); |
||||
} |
||||
|
||||
/** |
||||
* 订单查询 |
||||
* @param $outOrderNo |
||||
* @return array|\ByteDance\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function query($outOrderNo) |
||||
{ |
||||
$params['out_order_no'] = $outOrderNo; |
||||
return $this->request('ecpay/v1/query_order', $params); |
||||
} |
||||
} |
@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
|
||||
<?php |
||||
/* |
||||
* This file is part of the otkurbiz/bytedance. |
||||
* |
||||
* (c) |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Payment\Order; |
||||
use Pimple\Container; |
||||
use Pimple\ServiceProviderInterface; |
||||
|
||||
/** |
||||
* Class ServiceProvider. |
||||
* |
||||
* @author |
||||
*/ |
||||
class ServiceProvider implements ServiceProviderInterface |
||||
{ |
||||
/** |
||||
* {@inheritdoc}. |
||||
*/ |
||||
public function register(Container $app) |
||||
{ |
||||
!isset($app['order']) && $app['order'] = function ($app) { |
||||
return new Client($app); |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,56 @@
@@ -0,0 +1,56 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the OtkurBiz/ByteDance. |
||||
* |
||||
* (c) |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Payment\Refund; |
||||
|
||||
use ByteDance\Payment\Kernel\BaseClient; |
||||
|
||||
/** |
||||
* Class Client. |
||||
* |
||||
* @author |
||||
*/ |
||||
class Client extends BaseClient |
||||
{ |
||||
/** |
||||
* @param string $number |
||||
* @param string $refundNumber |
||||
* @param int $refundFee |
||||
* @param array $optional |
||||
* @return array|\ByteDance\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function byOutTradeNumber(string $number, string $refundNumber, int $refundFee, array $optional = []) |
||||
{ |
||||
return $this->refund($refundNumber, $refundFee, array_merge($optional, ['out_order_no' => $number])); |
||||
} |
||||
|
||||
/** |
||||
* @param string $refundNumber |
||||
* @param int $refundFee |
||||
* @param array $optional |
||||
* @return array|\ByteDance\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
protected function refund(string $refundNumber, int $refundFee, $optional = []) |
||||
{ |
||||
$params = array_merge([ |
||||
'out_refund_no' => $refundNumber, |
||||
'refund_amount' => $refundFee |
||||
], $optional); |
||||
|
||||
return $this->request('ecpay/v1/create_refund', $params); |
||||
} |
||||
} |
@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
|
||||
<?php |
||||
/* |
||||
* This file is part of the otkurbiz/bytedance. |
||||
* |
||||
* (c) |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Payment\Refund; |
||||
use Pimple\Container; |
||||
use Pimple\ServiceProviderInterface; |
||||
|
||||
/** |
||||
* Class ServiceProvider. |
||||
* |
||||
* @author |
||||
*/ |
||||
class ServiceProvider implements ServiceProviderInterface |
||||
{ |
||||
/** |
||||
* {@inheritdoc}. |
||||
*/ |
||||
public function register(Container $app) |
||||
{ |
||||
!isset($app['refund']) && $app['refund'] = function ($app) { |
||||
return new Client($app); |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,59 @@
@@ -0,0 +1,59 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the OtkurBiz/ByteDance. |
||||
* |
||||
* (c) |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Payment\Settle; |
||||
|
||||
use ByteDance\Payment\Kernel\BaseClient; |
||||
|
||||
/** |
||||
* Class Client. |
||||
* |
||||
* @author |
||||
*/ |
||||
class Client extends BaseClient |
||||
{ |
||||
/** |
||||
* 请求分账 |
||||
* @param string $settleNumber |
||||
* @param string $orderNumber |
||||
* @param string $desc |
||||
* @param array $optional |
||||
* @return array|\ByteDance\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function settle(string $settleNumber, string $orderNumber, string $desc = '分账', array $optional = []) |
||||
{ |
||||
$params = array_merge([ |
||||
'out_settle_no' => $settleNumber, |
||||
'out_order_no' => $orderNumber, |
||||
'settle_desc' => $desc |
||||
], $optional); |
||||
return $this->request('ecpay/v1/settle', $params); |
||||
} |
||||
|
||||
/** |
||||
* 请求分账 |
||||
* @param string $settleNumber |
||||
* @return array|\ByteDance\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \ByteDance\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function query(string $settleNumber, array $optional = []) |
||||
{ |
||||
$params = array_merge([ |
||||
'out_settle_no' => $settleNumber |
||||
], $optional); |
||||
return $this->request('ecpay/v1/settle', $params); |
||||
} |
||||
} |
@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
|
||||
<?php |
||||
/* |
||||
* This file is part of the otkurbiz/bytedance. |
||||
* |
||||
* (c) |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace ByteDance\Payment\Settle; |
||||
use Pimple\Container; |
||||
use Pimple\ServiceProviderInterface; |
||||
|
||||
/** |
||||
* Class ServiceProvider. |
||||
* |
||||
* @author |
||||
*/ |
||||
class ServiceProvider implements ServiceProviderInterface |
||||
{ |
||||
/** |
||||
* {@inheritdoc}. |
||||
*/ |
||||
public function register(Container $app) |
||||
{ |
||||
!isset($app['settle']) && $app['settle'] = function ($app) { |
||||
return new Client($app); |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/laravel-wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
return [ |
||||
/* |
||||
* 默认配置,将会合并到各模块中 |
||||
*/ |
||||
'defaults' => [ |
||||
/* |
||||
* 指定 API 调用返回结果的类型:array(default)/collection/object/raw/自定义类名 |
||||
*/ |
||||
'response_type' => 'array' |
||||
], |
||||
|
||||
'mini_program' => [ |
||||
'default' => [ |
||||
'app_id' => '', |
||||
'app_secret' => '', |
||||
], |
||||
], |
||||
]; |
Loading…
Reference in new issue